栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

LeetCode 4 第 k 小数详解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

LeetCode 4 第 k 小数详解

暴力做法

暴力做法 1:

时间复杂度达不到要求

需要寻找两个数组的中位数,直接开一个大数组,把两个有序数组合并到大数组中,再找最中间那个数就是答案

暴力做法 2:

用两个指针指向两个有序数组的起始位置,逐个扫描元素,哪个指针所对应的值更小,就把哪个指针往后移动,移动到第 9 次的时候,到达中位数的位置

每次删除一个

第一个指针从 1 移动到 2,代表把 1 抛弃了,从 2 移动到 3,代表把 2 抛弃了,每移动一个指针,代表抛弃一个,最终找到目标值

找第 k 小数分析

做法 1:

题目原来要求找两个有序数组的中位数,将问题转换成找两个有序数组合并之后第 k 小的数

每次删除 k / 2 个元素(比暴力方式删除的元素更多,效率更高),下一次删除前一个基础上剩余的 k / 2 个

下面模拟一下过程:

A 数组有 8 个元素,B 数组有 9 个元素,一共有 17 个元素,找它们合并之后第 9 小的数

第一轮,k = 9,k / 2 = 4,两个指针都指向第 4 个元素,比较这两个元素谁更小(选择小的部分删除),4 < 12,所以A 组中的这 k / 2 个元素肯定不包含第 k 小元素,直接把这 k / 2 个元素抛弃(一开始要找第 9 小数,由于已经把 4 个元素丢弃掉了,现在问题转换为找第 k - 4 个元素),然后移动指针,进行下一轮

第二轮,k = 5,k / 2 = 2,两个指针都指向第 2 个元素,比较这两个元素谁更小(选择小的部分删除),6 < 10,所以A 组中的这 k / 2 个元素肯定不包含第 k 小元素,直接把这 k / 2 个元素抛弃(一开始要找第 5 小数,由于已经把 2 个元素丢弃掉了,现在问题转换为找第 k - 4 - 2 个元素),然后移动指针,进行下一轮

第三轮,k = 3,k / 2 = 1,两个指针都指向第 1 个元素,比较这两个元素谁更小(选择小的部分删除),7 < 9,所以A 组中的这 k / 2 个元素肯定不包含第 k 小元素,直接把这 k / 2 个元素抛弃(一开始要找第 3 小数,由于已经把 1 个元素丢弃掉了,现在问题转换为找第 k - 4 - 2 - 1 个元素),然后移动指针,进行下一轮

 

第四轮,k = 2,k / 2 = 1,两个指针都指向第 1 个元素,比较这两个元素谁更小(选择小的部分删除),8 < 9,所以A 组中的这 k / 2 个元素肯定不包含第 k 小元素,直接把这 k / 2 个元素抛弃(一开始要找第 2 小数,由于已经把 1 个元素丢弃掉了,现在问题转换为找第 k - 4 - 2 - 1 - 1个元素),然后移动指针,进行下一轮

 

第五轮,k = 1,k / 2 = 0,找第 1 小的元素,A 数组的指针已经到尾,不能使用,那么我们就在 B 指针里面找第 1 小的元素,最终 9 就是第 k 小的元素

 

下面提出大部分题解都没有解释清楚的一些地方,真的让我很痛苦

下面详细分析一下疑问 2

要证明 A 数组中删除的这 k / 2 个元素一定是在中位数的左边

由于 A 数组是从小到大排序的,要保证这 k / 2 个数都在第 k 小数的左边,只需要保证最大的数 4 在第 k 小数的左边即可,也就是要证明最大的数 4 在第 k 小数的左边,也就是证明 4 右边的个数是否满足要求

保证最大的数 4 在第 k 小数的左边,4 右边的个数必须要大于等于 n - k(n - k 个数) + 1(第 k 个数)

首先两个数组都是有序的,4 右边的数大于 4,4 这个位置的数 < 12 这个位置的数,意味着12 右边的数都大于 12,肯定大于 4

要找 4 右边的个数:找比 4 大的数有多少个,看是否能填满,如果能填满甚至更多的话,就说明最大的数 4 一定是在第 k 小数的左边;如果比 4 大的数,不能填满右边的坑,就说明最大的数 4 不在第 k 小数的左边

A 数组、B数组中比 4 大的数是红框框起来的数:用总数 n 减去前面的数,红框框起来的数 n - (k / 2 * 2 - 1) 应该要大于等于右边的坑数 n - k + 1,才能够填满右边的坑

由于除法存在下取整,k 为奇数除以 2 向下取整,因此 / 2 * 2 要比原来的 k 要小 1

3 / 2 * 2,3 / 2 = 1,1 * 2 = 2

由于两种情况都成立,因此使用第 k 小数折半剔除的方法,可以证明每次剔除的数都在第 k 小数的左边的是成立的

概括

 

所谓的第 k 小 的数就是把两个有序的数组合并到一个大数组中,从左往右数第 k 个数就是答案,第一轮,k = 9,k / 2 = 4,两个指针都指向第 4 个元素,比较这两个元素谁更小(选择小的部分删除),4 < 12,所以A 组中的这 k / 2 个元素肯定不包含第 k 小元素,直接把这 k / 2 个元素抛弃(一开始要找第 9 小数,由于已经把 4 个元素丢弃掉了,现在问题转换为找第 k - 4 个元素),然后移动指针,进行下一轮

第一轮是从大数组的第 0 个位置开始数,第 9 个数就是我们的目标,第二轮 k 从 9 变成 5,也就是从大数组的 某个位置 开始数,第 5 个数就是我们的目标,虽然两轮数的个数不一样,但是最终的目标数都是同一个数,因为起点不一样

第二轮不再关心前面 4 个数字,只需要关心后面的这些数

相当于第一轮把所有的数字都填到坑里面,从左往右数,数到第 9 个就找到了,第二轮将红框框起来的那些数填到后面的坑里面,然后从左往右,数到第 5 个就是目标值

数据规模发生变化,剔除 4 个元素之后,数据规模从 n 变成 n - 4,每一轮都是从左往右数 k 个数,每一轮 k 的规模都一直减小

拓展

找第 k 小的数删除的 4 个元素是位于合并数组当中的前 4 个元素吗?

答案是否定的

可以发现第一轮删除的元素并不是前 4 个元素 ( 1、3、5、7并不是连续的,而是跳跃的 )

但是一定是在目标值第 k 小的数的前面

给了 3 个有序数组,一共 25 个数,求第 13 小的数,按照求两个有序数组第 k 小数的方式,第一次就把第 13 小的数删掉了( 3 个数组合并后从小到大排序,第 13 小的数就是 13)

可以发现如果给了 3 个数组,直接套用原来的折半删除是行不通的

如果给 2 个有序数组就折半删除,也就是每次删除 k / 2 个元素,如果给 3 个有序数组,就每次删除 k / 3 个元素

时间复杂度要求 O( log(m + n) )

要证明要删除的这 k / 3 个数是在第 k 小数的左边

前面折半删除的例子证明的是比 4 大的数能不能填满这些坑

证明 4 左边比 4 小的数能够填多少个坑,如果比 4 小的数足够多,就有可能把 4 挤到 k 右边的位置,如果比 4 小的数没有这么多,4 就可以放在 k 前面的位置,或者更左边的位置

关心一下 4 加上比 4 小的个数总共有多少个,如果个数是小于 k 个,那么 4 一定是在第 k 小数的左边,如果个数是大于 k 个,就有可能把 4 挤到 k 右边的位置,就不满足要求了

现在比较 k / 3 = 4 的位置上比较 4、12、21,发现 4 最小,需要删除这 k / 3个数

现在确定 1、2、3 小于 4,4 右边的数大于 4,与 4 对应的另外两个指针上的数也都大于 4,因此它们后面的数也是大于 4 的,但是它们前面的数根据已知条件没有办法确定,假设这些不确定的数都是比 4 小的( 尽可能将比 4 小的数弄多一点,看是否足够多能够把 4 挤到 k 右边的位置 )

4 加上比 4 小的总个数就是 k / 3 * 3 - 2 个,我们希望它是小于 k 个,这样 4 才能在第 k 小数的左边

k / 3 * 3 有三种情况,注意下取整,第一种 k 恰好是 3 的倍数( / 3 * 3 还是等于 k ),第二种 k 恰好是 3 的倍数多 1( / 3 * 3 相当于减掉一个 1 ),第三种情况 k 恰好是 3 的倍数多 2( / 3 * 3 相当于减掉一个 2 ),可以带入值去理解:k = 3、4、5

由此可以通过这 3 种情况证明 4 加上比 4 小的总个数是小于 k 个,4 在第 k 小数的左边,最终可以证明 删除的这 k / 3 个数是在第 k 小数的左边 这句话是成立的

下面来证明方案是否满足题目要求的时间复杂度,左边是折半删除:一开始假设个数是 n 个,每进行一次操作变成原规模的 1 / 2(每次删除 k / 2 个),进行 x 次操作后,最终规模缩减到 1,每次删除 k / 3 个的时间复杂度,每进行一次操作变成原规模的 2 / 3,进行 x 次操作后,最终规模缩减到 1,递归结束,两者的时间复杂度都是 O( log( m + n ) )

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/953674.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号