🤚我的博客
- 欢迎光临我的博客:
https://blog.csdn.net/qq_52434217?type=blog
🥛前言
来了来了他终于带着算法题的解析来了!困扰依旧的算法终于被语言文字输出来了!
两个有序数组的中位数求解
题目
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
要求
算法的时间复杂度应该为 O(log (m+n))
。
分析
初步推理
本题是寻找两个数组拼在一起的中位数,假设中位数的在这两个数组的位置为第k
个,换言之就是找第k
小的数。考虑假设两个数组一共3
个数,则中位数是第2个,假设两个数组一共4个数,则中位数是第2个和第3个数求平均。寻找规律后发现需要分情况讨论,如果两个数组一共m+n
个数,当n+m
为奇数时,那么中位数就是第(n+m)/2+1
小的数;当n+m
为偶数时,那么中位数就是第(n+m)/2
小的数和(n+m)/2+1
小的数;本题要求时间复杂度为 O(log (m+n))
,自然需要采用二分法。那么如何使用二分法呢?
进一步推理
首先这两个数组是顺序数组,我们分别比较两个数组中第i
个值。如果nums1[i-1]>nums2[i-1]
,就能推出在nums2
中有i-1
个数比nums1[i-1]
小。这里假设nums1={1,2,4}
, nums2={1,2,3,4,5,6,7,8,9,10}
,则k=7。由于采用二分法,这里我们取i=k/2
。当我们比较i=k/2=3
时的值发现nums1[2]>nums2[2]
,这时可以推出nums1[2
]最多为第6小的数,但是我们排除了前3
小的数。于是在剩下的数字中按照同样的思路去查找第7-3=4
小的数,就可以再排除3
个,直到排除前k-1
个。这里有一个细节就是如果取等于的时候,我们可以只挪动其中一个数组的下标。如果nums1的长度过小怎么办呢,比如k/2=4,而nums1长度为3。这个时候就只需要在nums2中找到值就行了。
思路与代码的转换
核心思路
在前面我们已经知道如何找到找到第k
小的数,那么如何表示剩下的数字呢。其实我们可以直接将整个数组传进去,然后指定start
和end
,这样我们就可以知道一个数组的全部值了。如何得到start
和end
值呢?我们首先传入的肯定是整个数组,那么start
为0
,end
为m-1
或者n-1
。以nums1
为例,在nums1
中找第k/2
个数,就是找下标为0+k/2-1
的数。如果nums1[i]>nums2[i]
,那么再递归时就传入nums1
从start到end
的数和nums2
第i+1
个数到end
的数。同时我们已经排除了nums2中的i个数,所以在递归时,要传入k-(i-start+1)
以用来找到第k-(i-start+1)
个数。但是需要考虑一个下标越界的问题,所以i
值应该为start+Math.min(m,k/2)-1
,这样一来两个数组就需要两个变量指向下标,分别为i
和j
。
转化成代码为
int i = start1 + Math.min(len1, k / 2) - 1;
int j = start2 + Math.min(len2, k / 2) - 1;
if (nums1[i] > nums2[j]) {
return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
} else {
return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
}
什么时候完成递归返回结果呢?这里可以用上面的例子,分两种情况,一种是nums1
取完,另一种是排除完前k-1
个数。当nums1
被取完仍没有排除掉前k-1
个数,这里假设已经排除了x
个,那么还有k-x
个,则可以取剩余的nums2
中第k-x
个。前面已经提到nums2
的剩余数组是由start
和end
决定的,又由于我们递归传入的是滴k=k-x
个值,所以返回nums2[start+k-1]
就可以。如果已经排除了前k-1
个值,那么只需要返回第k=1
小的数,即nums1[start]
和nums2[start]
最小值即可。
if (len1 == 0) return nums2[start2 + k - 1];
if (k == 1) return Math.min(nums1[start1], nums2[start2]);
细节
如何知道一定是nums1
长度不够呢,这里我们可以先计算两个数组的长度,如果nums1
长于nums2
就交换。
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
最后解决就奇偶长度的问题。我们直接将利用(n+m+1)/2
和(n+m+2)/2
即可,即利用下面的代码解决即可。
int left = (n + m + 1) / 2;
int right = (n + m + 2) / 2;
(getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
这里我们发现当n+m
为奇数时,(n+m+1)/2==(n+m+2)/2
,当n+m
为偶数时,(n+m+2)/2==(n+m+1)/2+1
。
完整解题代码
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int k1 = (n + m + 1) / 2;
int k2 = (n + m + 2) / 2;
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, k1) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, k2)) * 0.5;
}
private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
if (len1 == 0) return nums2[start2 + k - 1];
if (k == 1) return Math.min(nums1[start1], nums2[start2]);
int p1 = start1 + Math.min(len1, k / 2) - 1;
int p1 = start2 + Math.min(len2, k / 2) - 1;
if (nums1[p1] > nums2[p2]) {
return getKth(nums1, start1, end1, nums2, p2 + 1, end2, k - (p2 - start2 + 1));
} else {
return getKth(nums1, p1 + 1, end1, nums2, start2, end2, k - (p1 - start1 + 1));
}
}
}
END
总结
1、当复杂度要求为log时,需要采用二分法递归
2、当遇到奇偶情况时,可以采用(n+m+1)/2
和(n+m+2)/2
这个关系解决
3、在遇到此类数组截断的情况时,可以传入起始点和终止点而不用遍历。
参考资料
[1] 链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/8999/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/ 作者:windliang 来源:力扣(LeetCode)
公众号
更多内容请关注小夜的公众号,将持续更新java spring全家桶,vue全家桶,python数据分析与爬虫以及js与爬虫逆向。
欢迎关注小夜的公众号