[LeetCode] Median of Two Sorted Arrays 两个有序数组的中位数
There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
You may assume nums1 and nums2 cannot be both empty.
Example 1:
nums1 = [1, 3]
nums2 = [2] The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4] The median is (2 + 3)/2 = 2.5
这道题让我们求两个有序数组的中位数,而且限制了时间复杂度为 O(log (m+n)),看到这个时间复杂度,自然而然的想到了应该使用二分查找法来求解。但是这道题被定义为 Hard 也是有其原因的,难就难在要在两个未合并的有序数组之间使用二分法,如果这道题只有一个有序数组,让求中位数的话,估计就是个 Easy 题。对于这道题来说,可以将两个有序数组混合起来成为一个有序数组再做吗,图样图森破,这个时间复杂度限制的就是告诉你金坷垃别想啦。还是要用二分法,而且是在两个数组之间使用,感觉很高端啊。回顾一下中位数的定义,如果某个有序数组长度是奇数,那么其中位数就是最中间那个,如果是偶数,那么就是最中间两个数字的平均值。这里对于两个有序数组也是一样的,假设两个有序数组的长度分别为m和n,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。为了简化代码,不分情况讨论,使用一个小 trick,分别找第 (m+n+1) / 2 个,和 (m+n+2) / 2 个,然后求其平均值即可,这对奇偶数均适用。若 m+n 为奇数的话,那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等,相当于两个相同的数字相加再除以2,还是其本身。
好,这里需要定义一个函数来在两个有序数组中找到第K个元素,下面重点来看如何实现找到第K个元素。首先,为了避免拷贝产生新的数组从而增加时间复杂度,使用两个变量i和j分别来标记数组 nums1 和 nums2 的起始位置。然后来处理一些 corner cases,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果 K=1 的话,只要比较 nums1 和 nums2 的起始位置i和j上的数字就可以了。难点就在于一般的情况怎么处理?因为需要在两个有序数组中找到第K个元素,为了加快搜索的速度,可以使用二分法,那么对谁二分呢,数组么?其实要对K二分,意思是需要分别在 nums1 和 nums2 中查找第 K/2 个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第 K/2 个数字,所以需要先 check 一下,数组中到底存不存在第 K/2 个数字,如果存在就取出来,否则就赋值上一个整型最大值(目的是要在 nums1 或者 nums2 中先淘汰 K/2 个较小的数字,判断的依据就是看 midVal1 和 midVal2 谁更小,但如果某个数组的个数都不到 K/2 个,自然无法淘汰,所以将其对应的 midVal 值设为整型最大值,以保证其不会被淘汰),若某个数组没有第 K/2 个数字,则淘汰另一个数组的前 K/2 个数字即可。举个例子来说吧,比如 nums1 = {3},nums2 = {2, 4, 5, 6, 7},K=4,要找两个数组混合中第4个数字,则分别在 nums1 和 nums2 中找第2个数字,而 nums1 中只有一个数字,不存在第二个数字,则 nums2 中的前2个数字可以直接跳过,为啥呢,因为要求的是整个混合数组的第4个数字,不管 nums1 中的那个数字是大是小,第4个数字绝不会出现在 nums2 的前两个数字中,所以可以直接跳过。
有没有可能两个数组都不存在第 K/2 个数字呢,这道题里是不可能的,因为K不是任意给的,而是给的 m+n 的中间值,所以必定至少会有一个数组是存在第 K/2 个数字的。最后就是二分法的核心啦,比较这两个数组的第 K/2 小的数字 midVal1 和 midVal2 的大小,如果第一个数组的第 K/2 个数字小的话,那么说明要找的数字肯定不在 nums1 中的前 K/2 个数字,可以将其淘汰,将 nums1 的起始位置向后移动 K/2 个,并且此时的K也自减去 K/2,调用递归,举个例子来说吧,比如 nums1 = {1, 3},nums2 = {2, 4, 5},K=4,要找两个数组混合中第4个数字,那么分别在 nums1 和 nums2 中找第2个数字,nums1 中的第2个数字是3,nums2 中的第2个数字是4,由于3小于4,所以混合数组中第4个数字肯定在 nums2 中,可以将 nums1 的起始位置向后移动 K/2 个。反之,淘汰 nums2 中的前 K/2 个数字,并将 nums2 的起始位置向后移动 K/2 个,并且此时的K也自减去 K/2,调用递归即可,参见代码如下:
C++ 解法一:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size(), left = (m + n + ) / , right = (m + n + ) / ;
return (findKth(nums1, , nums2, , left) + findKth(nums1, , nums2, , right)) / 2.0;
}
int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) {
if (i >= nums1.size()) return nums2[j + k - ];
if (j >= nums2.size()) return nums1[i + k - ];
if (k == ) return min(nums1[i], nums2[j]);
int midVal1 = (i + k / - < nums1.size()) ? nums1[i + k / - ] : INT_MAX;
int midVal2 = (j + k / - < nums2.size()) ? nums2[j + k / - ] : INT_MAX;
if (midVal1 < midVal2) {
return findKth(nums1, i + k / , nums2, j, k - k / );
} else {
return findKth(nums1, i, nums2, j + k / , k - k / );
}
}
};
Java 解法一:
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length, left = (m + n + ) / , right = (m + n + ) / ;
return (findKth(nums1, , nums2, , left) + findKth(nums1, , nums2, , right)) / 2.0;
}
int findKth(int[] nums1, int i, int[] nums2, int j, int k) {
if (i >= nums1.length) return nums2[j + k - ];
if (j >= nums2.length) return nums1[i + k - ];
if (k == ) return Math.min(nums1[i], nums2[j]);
int midVal1 = (i + k / - < nums1.length) ? nums1[i + k / - ] : Integer.MAX_VALUE;
int midVal2 = (j + k / - < nums2.length) ? nums2[j + k / - ] : Integer.MAX_VALUE;
if (midVal1 < midVal2) {
return findKth(nums1, i + k / , nums2, j, k - k / );
} else {
return findKth(nums1, i, nums2, j + k / , k - k / );
}
}
}
上面的解法一直使用的是原数组,同时用了两个变量来分别标记当前的起始位置。我们也可以直接生成新的数组,这样就不要用起始位置变量了,不过拷贝数组的操作可能会增加时间复杂度,也许会超出限制,不过就算当个思路拓展也是极好的。首先要判断数组是否为空,为空的话,直接在另一个数组找第K个即可。还有一种情况是当 K = 1 时,表示要找第一个元素,只要比较两个数组的第一个元素,返回较小的那个即可。这里分别取出两个数组的第 K/2 个数字的位置坐标i和j,为了避免数组没有第 K/2 个数组的情况,每次都和数组长度做比较,取出较小值。这里跟上面的解法有些许不同,上面解法直接取出的是值,而这里取出的是位置坐标,但是思想都是很类似的。不同在于,上面解法中每次固定淘汰 K/2 个数字,而这里由于取出了合法的i和j,所以每次淘汰i或j个。评论区有网友提出,可以让 j = k-i,这样也是对的,可能还更好一些,收敛速度可能会更快一些,参见代码如下:
C++ 解法二:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
return (findKth(nums1, nums2, (m + n + ) / ) + findKth(nums1, nums2, (m + n + ) / )) / 2.0;
}
int findKth(vector<int> nums1, vector<int> nums2, int k) {
if (nums1.empty()) return nums2[k - ];
if (nums2.empty()) return nums1[k - ];
if (k == ) return min(nums1[], nums2[]);
int i = min((int)nums1.size(), k / ), j = min((int)nums2.size(), k / );
if (nums1[i - ] > nums2[j - ]) {
return findKth(nums1, vector<int>(nums2.begin() + j, nums2.end()), k - j);
} else {
return findKth(vector<int>(nums1.begin() + i, nums1.end()), nums2, k - i);
}
return ;
}
};
Java 解法二:
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length, left = (m + n + 1) / 2, right = (m + n + 2) / 2;
return (findKth(nums1, nums2, left) + findKth(nums1, nums2, right)) / 2.0;
}
int findKth(int[] nums1, int[] nums2, int k) {
int m = nums1.length, n = nums2.length;
if (m == 0) return nums2[k - 1];
if (n == 0) return nums1[k - 1];
if (k == 1) return Math.min(nums1[0], nums2[0]);
int i = Math.min(m, k / 2), j = Math.min(n, k / 2);
if (nums1[i - 1] > nums2[j - 1]) {
return findKth(nums1, Arrays.copyOfRange(nums2, j, n), k - j);
} else {
return findKth(Arrays.copyOfRange(nums1, i, m), nums2, k - i);
}
}
}
此题还能用迭代形式的二分搜索法来解,是一种相当巧妙的应用,这里就参照 stellari 大神的帖子 来讲解吧。所谓的中位数,换一种角度去看,其实就是把一个有序数组分为长度相等的两段,中位数就是前半段的最大值和后半段的最小值的平均数,也就是离分割点相邻的两个数字的平均值。比如说对于偶数个数组 [1 3 5 7],那么分割开来就是 [1 3 / 5 7],其中 '/' 表示分割点,中位数就是3和5的平均值。对于奇数个数组 [1 3 4 5 7],可以分割为 [1 3 4 / 4 5 7],可以发现左右两边都有个4,则中位数是两个4的平均数,还是4。这里使用L表示分割点左边的数字,R表示分割点右边的数字,则对于 [1 3 5 7] 来说,L=3,R=5。对于 [1 3 4 5 7] 来说,L=4,R=4。那么对于长度为N的数组来说,可以分别得到L和R的位置,如下所示:
N Index of L Index of R
观察上表,可以得到规律,Idx(L)= (N-1)/2,idx(R) = N/2,所以中位数可以用下式表示:
(L + R) / = (A[(N - ) / ] + A[N / ]) /
为了统一数组长度为奇数和偶数的情况,可以使用一个小 tricky,即在每个数字的两边都加上一个特殊字符,比如井号,这个 tricky 其实在马拉车算法中也使用过,可以参见博主之前的帖子 Manacher's Algorithm 马拉车算法。这样做的好处是不管奇数或者偶数,加井号后数组的长度都是奇数,并且切割点的位置也是确定的,比如:
[ ] -> [# # # # #] N =
index newN = [ ] -> [# # # # # #] N =
index newN =
这里的N是原数组的长度,newN 是添加井号后新数组的长度,可以发现 newN = 2N+1,而且切割点永远都在新数组中坐标为N的位置,且 idx(L) = (N-1)/2,idx(R) = N/2,这里的N就可以换成分割点的位置,岂不美哉(注意这里的 idx(L) 和 idx(R) 表示的是在未填充#号的坐标位置)!现在假设有两个数组:
[ ] -> [# # # # # #] N1 =
index newN1 = [ ] -> [# # # # #] N2 =
index newN2 =
跟只有一个数组的情况类似,这里需要找到一个切割点,使得其分别可以将两个数组分成左右两部分,需要满足的是两个左半边中的任意一个数字都要小于两个右半边数组的数字,注意这里可能有的左半边或右半边会为空,但是两个左半边数字的个数和应该等于两个右半边的个数和。这里还可以观察出一些规律:
1. 总共有 2N1 + 2N2 + 2 个位置,那么除去两个分割点,两个左右半边应该各有 N1 + N2 个数字。
2. 因此,对于一个在 A2 数组中的分割点位置 C2 = K,在 A1 数组中的位置应该为 C1 = N1 + N2 - K,比如假如在 A2 中的分割点位置为 C2 = 2,那么在 A1 中的位置为 C1 = 4 + 5 - C2 = 7。
[# # # # (/) # #] [# / # # #]
3. 假如两个数组都被分割了,那么就应该会有两个L和R,分别是:
L1 = A1[(C1 - ) / ]
R1 = A1[C1 / ] L2 = A2[(C2 - ) / ]
R2 = A2[C2 / ]
对于上面的例子就有:
L1 = A1[( - ) / ] = A1[] =
R1 = A1[ / ] = A1[] = L2 = A2[( - ) / ] = A2[] =
R2 = A2[ / ] = A2[] =
现在需要检测这个切割点是否是正确的中位数的切割点,那么根据之前的分析,任意的左半边的数字都需要小于等于右半边的数字,L1 和 L2 是左半边的最大的数字,R1 和 R2 是右半边的最小的数字,所以需要满足下列关系:
L1 <= R1 && L1 <= R2 && L2 <= R1 && L2 <= R2
由于两个数组都是有序的,所以 L1 <= R1 和 L2 <= R2 都是满足的,那么就只需要满足下列的不等式即可:
L1 <= R2 && L2 <= R1
这样的话就可以利用二分搜索了,假如 L1 > R2 的话,说明数组 A1 的左半边的数字过大了,需要把切割点 C1 往左移动。假如 L2 > R1,说明数组 A2 的左半边数字过大,需要把分割点 C2 左移。若满足上面的条件,说明当前切割点就是正确的,那么中位数就可以求出来了,即为:
(max(L1, L2) + min(R1, R2)) /
最后还有两点注意事项:
1. 由于 C1 和 C2 是可以互相计算而得,即一个确定了,另一个就可以计算出来了。所以尽量去移动较短的那个数组,这样得到的时间复杂度为 O(lg(min(N1, N2)))。
2. 对于 corner case 的处理,当切割点在 0 或者 2n 的位置时,将L或R的值分别赋值为整型最小值和最大值,这不会改变正确的切割点的位置,会使得代码实现更加方便。
C++ 解法三:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
if (m < n) return findMedianSortedArrays(nums2, nums1);
if (n == ) return ((double)nums1[(m - ) / ] + (double)nums1[m / ]) / 2.0;
int left = , right = n * ;
while (left <= right) {
int mid2 = (left + right) / ;
int mid1 = m + n - mid2;
double L1 = mid1 == ? INT_MIN : nums1[(mid1 - ) / ];
double L2 = mid2 == ? INT_MIN : nums2[(mid2 - ) / ];
double R1 = mid1 == m * ? INT_MAX : nums1[mid1 / ];
double R2 = mid2 == n * ? INT_MAX : nums2[mid2 / ];
if (L1 > R2) left = mid2 + ;
else if (L2 > R1) right = mid2 - ;
else return (max(L1, L2) + min(R1, R2)) / ;
}
return -;
}
};
Java 解法三:
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
if (m < n) return findMedianSortedArrays(nums2, nums1);
if (n == 0) return (nums1[(m - 1) / 2] + nums1[m / 2]) / 2.0;
int left = 0, right = 2 * n;
while (left <= right) {
int mid2 = (left + right) / 2;
int mid1 = m + n - mid2;
double L1 = mid1 == 0 ? Double.MIN_VALUE : nums1[(mid1 - 1) / 2];
double L2 = mid2 == 0 ? Double.MIN_VALUE : nums2[(mid2 - 1) / 2];
double R1 = mid1 == m * 2 ? Double.MAX_VALUE : nums1[mid1 / 2];
double R2 = mid2 == n * 2 ? Double.MAX_VALUE : nums2[mid2 / 2];
if (L1 > R2) left = mid2 + 1;
else if (L2 > R1) right = mid2 - 1;
else return (Math.max(L1, L2) + Math.min(R1, R2)) / 2;
}
return -1;
}
}
Github 同步地址:
https://github.com/grandyang/leetcode/issues/4
参考资料:
https://leetcode.com/problems/median-of-two-sorted-arrays/
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] Median of Two Sorted Arrays 两个有序数组的中位数的更多相关文章
- [LeetCode] 4. Median of Two Sorted Arrays 两个有序数组的中位数
There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two ...
- [LintCode] Median of Two Sorted Arrays 两个有序数组的中位数
There are two sorted arrays A and B of size m and n respectively. Find the median of the two sorted ...
- 004 Median of Two Sorted Arrays 两个有序数组的中位数
There are two sorted arrays nums1 and nums2 of size m and n respectively.Find the median of the two ...
- Leetcode4.Median of Two Sorted Arrays两个排序数组的中位数
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 . 请找出这两个有序数组的中位数.要求算法的时间复杂度为 O(log (m+n)) . 你可以假设 nums1 和 nums2 不同 ...
- 2.Median of Two Sorted Arrays (两个排序数组的中位数)
要求:Median of Two Sorted Arrays (求两个排序数组的中位数) 分析:1. 两个数组含有的数字总数为偶数或奇数两种情况.2. 有数组可能为空. 解决方法: 1.排序法 时间复 ...
- 【LeetCode】4.Median of Two Sorted Arrays 两个有序数组中位数
题目: There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the ...
- 【medium】4. Median of Two Sorted Arrays 两个有序数组中第k小的数
There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two ...
- 4. Median of Two Sorted Arrays(2个有序数组的中位数)
There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two ...
- Median of Two Sorted 求两个有序数组的中位数
中位数是把一个数的集合划分为两部分,每部分包含的数字个数相同,并且一个集合中的元素均大于另一个集合中的元素. 因此,我们考虑在一个任意的位置,将数组A划分成两部分.i表示划分数组A的位置,如果数组A包 ...
随机推荐
- CE修改器修改DNF 测试视频 阿修罗提升智力增加攻击力
使用CE修改器来修改网络游戏,如DNF 测试视频: CE修改器:指的是Cheat Engine,字面上的意思指的是作弊引擎的意思,是一款内存修改编辑工具.通过修改游戏的内存数据来得到一些原本无法实现的 ...
- 【中文分词】条件随机场CRF
之前介绍的MMEM存在着label bias问题,因此Lafferty et al. [1] 提出了CRF (Conditional Random Field). BTW:比较有意思的是,这篇文章的二 ...
- 关闭firefox的plugincheck
每次打开firefox都弹出这个SB页面: https://www.mozilla.org/en-US/plugincheck/ 关不掉, 很是烦人. 经过地番google,找到了答案: about: ...
- Atitit. Atiposter 发帖机 新特性 poster new feature v7 q39
Atitit. Atiposter 发帖机 新特性 poster new feature v7 q39 V8 重构iocutilV4,use def iocFact...jettyUtil V ...
- ubuntu设置时区为美国中部时间西六区
查看当前ubuntu系统时区 date -R Fri, Dec :: + 显示的是东八区时间及北京时间 然后输入tzselect 按照提示修改对应时区 本例子修改为美国中部时间 西六区 ~$ tzse ...
- [deviceone开发]-openPage的动画效果示例
一.简介do_App的openPage支持16种过场动画,这个示例直观的展示16种动画的效果.适合初学者.二.效果图三.相关下载https://github.com/do-project/code4d ...
- js基础(改变透明度实现轮播图的算法)
前面有分享过改变层级的轮播图算法,今天继续利用透明度来实现无位移的轮播图算法. 实现逻辑:将所有要轮播的图片全部定位到一起,即一层一层摞起来,并且利用层级的属性调整正确的图片顺序,将图片的透明度全部设 ...
- swift-分支
swift相当于OC的比较 if后的括号可以省略 if后只能接bool值 if后的大括号不能省略 let num1 = 5.0 let num2 = 4.0 let boo :Bool = true ...
- 0038 Java学习笔记-多线程-传统线程间通信、Condition、阻塞队列、《疯狂Java讲义 第三版》进程间通信示例代码存在的一个问题
调用同步锁的wait().notify().notifyAll()进行线程通信 看这个经典的存取款问题,要求两个线程存款,两个线程取款,账户里有余额的时候只能取款,没余额的时候只能存款,存取款金额相同 ...
- MySQL误操作后如何快速恢复数据
基本上每个跟数据库打交道的程序员(当然也可能是你同事)都会碰一个问题,MySQL误操作后如何快速回滚?比如,delete一张表,忘加限制条件,整张表没了.假如这还是线上环境核心业务数据,那这事就闹大了 ...