来源:https://leetcode.com/problems/median-of-two-sorted-arrays/
解答来源:
https://www.cnblogs.com/grandyang/p/4465932.html
解答代码:
1 class Solution { 2 public: 3 double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { 4 int m = nums1.size(), n = nums2.size(), left = (m + n + 1) / 2, right = (m + n + 2) / 2; 5 return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0; 6 } 7 int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) { 8 if (i == nums1.size()) return nums2[j + k - 1]; 9 if (j == nums2.size()) return nums1[i + k - 1]; 10 if (k == 1) return min(nums1[i], nums2[j]); 11 int midVal1 = (i + k / 2 - 1 < nums1.size()) ? nums1[i + k / 2 - 1] : INT_MAX; 12 int midVal2 = (j + k / 2 - 1 < nums2.size()) ? nums2[j + k / 2 - 1] : INT_MAX; 13 if (midVal1 < midVal2) { 14 return findKth(nums1, i + k / 2, nums2, j, k - k / 2); 15 } else { 16 return findKth(nums1, i, nums2, j + k / 2, k - k / 2); 17 } 18 } 19 };
本题有些硬核了,解答也是看了很长时间才明白。
本题要找两个数组的中位数,正如解答中所说的那样,对于奇数,中位数只有一个。但是对于偶数,中位数是两个数字的平均值。解答里用到了一个很有用的、可以避免分类讨论的技巧:求(m+n+1)/2和(m+n+2)/2的平均值。如果m+n是奇数,(m+n+1)/2的结果就是中间值,而(m+n+2)/2也是这个值;而如果m+n是偶数,(m+n+1)/2还是原来那个前半个的值(因为相对于加了个0.5,整数不体现),而(m+n+2)/2就是下一个值。
此时问题就转化为了如何求第(m+n+1)/2和(m+n+2)/2的值,一般地,其实就是求第k个值,从这里就有些烧脑了。
第一步,分别在两个数组中求第k/2个值并比较大小。如果有一个数组的长度达不到k/2,说明另外一个数组的k/2中也不存在第K个值(因为即使把第一个数组都拼进第二个数组的前k/2个中来也凑不够k个),所以我们可以知道第二个数组的前k/2部分是小于第K个的(也就是说,我们知道了这k/2部分在拼起来的数组中的位置了)。那么,如果我们把这部分在总的数组中删掉,那是不是第k个前面少了k/2个?所以,这种情况下我们只需要找k-k/2个就好了!所以我们把指向第二个数组的开始指针向后移到k/2,同时给k减去K/2。而对于一般情况,如果第一个数组找到的k/2小于第二个数组中找到的,说明第一个数组的前k/2部分中一定找不到第k个数,因为其即使只和第二个数组中的前k/2组合,也被排在了前面。这是就删去这部分,同时减k。
第二步,我们需要思考一下算法的停机性。很显然,每次给k除以2是永远也变不成0的,算法应该在什么时候结束呢?应该就是k=1的时候,因为这个时候从宏观来看,要找的实际上是数组的第一个值,也就是“现存的”数组中的最小值。所以我们只需要比较两个数组的第一个值,返回较小的那个就好了!
但仅仅是这样就完成了吗?很显然,边界情况不止有k这一个(其实,leetcode的题目考察的重点只有两个:idea和 corner case,所以在做题时要对各种边界情况充分考虑),还有一种情况,就是我们在移动指针的时候,指针正好指向了数组的末尾元素后一个将数组变成了空数组。为什么这里说的是恰好后一个呢?因为如果移出有一个及以上的空位,说明这个数组的长度是不够k/2的,这种情况我们一开始已经分析处理过了!(实际上,原答案并没有确定是后一个,我在分析时产生了疑惑,将原答案的代码中的i <= nums1.size()改为了i==nums1.size(),依然通过了leetcode的测试,验证了我的想法)而对于这种情况,因为这个数组已经是空的了,我们只需要在另外一个数组中找我们要的第k个就好了。
实际上,对于第一种情况中说的那种特殊情况,原答案用了一个常数INT_MAX来很好地将它统一到了一般情况的处理中:如果第一个数组小,说明要减第二个数组。而这里的规则是两个数组中k/2个谁小减谁,所以将此时的值赋为INT_MAX,来保证一定减第二个。
注意,最后return时候的除法一定要用2.0而不是2。答案要求返回的是double类型,使用2会降为Int,只有使用2.0这一double类型才能保证结果也是double的。
另外,这里再说一个点。之前自己看了原答案后又自己写了一遍,提交时候发现无论是时间还是空间都比原答案大了几乎一倍!
后来发现了问题所在,原来原答案在构造函数时使用的是vector<int>&,也就是引用类型,而我写的则是vector<int>。只是一个符号的差别,就导致了我的程序在每次递归时都需要在活动记录把这个容器拷贝一遍,空间和时间开销大也就不难理解了。这点也是之后要注意的一点。