寻找两个正序数组的中位数
题意
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
进阶:你 能设计一个时间复杂度为 $O(log (m+n)) $的算法解决此问题吗?
思路
取两个数组第k个元素
假设存在两个数组 A , B A,B A,B, A [ k / 2 − 1 ] A[k/2-1] A[k/2−1]和 B [ k / 2 − 1 ] B[k/2-1] B[k/2−1]前有 [ 0 ⋯ k / 2 − 2 ] [0 \cdots k/2-2] [0⋯k/2−2],各有 k / 2 − 1 k/2-1 k/2−1个元素,共 k − 2 k-2 k−2个元素。比较 A [ k / 2 − 1 ] A[k/2-1] A[k/2−1]和 B [ k / 2 − 1 ] B[k/2-1] B[k/2−1]。假设 A [ k / 2 − 1 ] A[k/2-1] A[k/2−1]是较小者,那么 A [ k / 2 − 1 ] A[k/2-1] A[k/2−1]不是第k个元素,因为 A [ k / 2 − 1 ] A[k/2-1] A[k/2−1]之前最多也只有 k − 2 k-2 k−2 个元素, A [ k / 2 − 1 ] A[k/2-1] A[k/2−1]最多也只是第 k − 1 k-1 k−1位元素。从而得到 A [ 0 ⋯ k / 2 − 1 ] A[0 \cdots k / 2-1] A[0⋯k/2−1] 均不是第 k k k 小元素。将 A [ 0 ⋯ k / 2 − 1 ] A[0\cdots k/2-1] A[0⋯k/2−1]丢弃,得到新的数组。这样搜索范围就缩小了一半。同时原来的查找范围也从查找第 k k k个元素,变成了第 k ′ k' k′个元素, k ′ = k − k / 2 k'=k-k/2 k′=k−k/2。然后重复上述的查找过程,直到 k = 1 k=1 k=1,找到结果 。
为什么每次二分的增量为 k − 2 2 \frac{k-2}{2} 2k−2 而不是 k 2 \frac{k}{2} 2k或者 k − 1 2 \frac{k-1}{2} 2k−1
如果 i n c r = k / 2 incr=k/2 incr=k/2或者 i n c r = ( k − 1 ) / 2 incr=(k-1)/2 incr=(k−1)/2,那么被舍弃的数组段,可能包含第 k k k个元素。
如果 i n c r = ( k − 1 ) / 2 incr=(k-1)/2 incr=(k−1)/2,那么 A [ 0... ( k − 1 ) / 2 ] A[0...(k-1)/2] A[0...(k−1)/2]和 B [ 0... ( k − 1 ) / 2 ] B[0...(k-1)/2] B[0...(k−1)/2],各有 ( k − 1 ) / 2 + 1 (k-1)/2+1 (k−1)/2+1个元素。现在假设 A [ ( k − 1 ) / 2 ] > B [ ( k − 1 ) / 2 ] A[(k-1)/2] > B[(k-1)/2] A[(k−1)/2]>B[(k−1)/2] 那么 B [ 0... ( k − 1 ) / 2 ] B[0...(k-1)/2] B[0...(k−1)/2]数据段被舍弃。若k为奇数, A [ ( k − 1 ) / 2 ] A[(k-1)/2] A[(k−1)/2]和 B [ ( k − 1 ) / 2 ] B[(k-1)/2] B[(k−1)/2]有可能为合并后有序数组的第 k + 1 k+1 k+1位,那么被舍弃的 A [ 0... ( k − 1 ) / 2 ] A[0...(k-1)/2] A[0...(k−1)/2]可能就包含了第 k k k位元素,把所求的答案舍弃了。
i n c r = k / 2 incr=k/2 incr=k/2同理,同样可能舍弃所求答案。
i n c r = ( k − 2 ) / 2 incr=(k-2)/2 incr=(k−2)/2, A [ ( k − 2 ) / 2 ] A[(k-2)/2] A[(k−2)/2]和 B [ ( k − 2 ) / 2 ] B[(k-2)/2] B[(k−2)/2]较大者被保留,较大者有可能为第 k k k元素,被舍弃的较小数据段不可能包含第 k k k个元素。
AC 代码
class Solution {
public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len = nums1.length + nums2.length;
if (len % 2 == 0) {
return 1.0 * (findKthElement(nums1, nums2, len / 2) + findKthElement(nums1, nums2, len / 2 + 1)) / 2;
} else {
return findKthElement(nums1, nums2, len / 2 + 1);
}
}
public static double findKthElement(int[] nums1, int[] nums2, int k) {
// p1,p2为指针,指的是nums1[0..p1-1]元素排除不含第k个元素,nums1[p1..]可能有第k个元素
int p1 = 0, p2 = 0;
// incr1,incr2为增量偏移,nums1[p1+incr1]为nums1数组中,下一次判断的轴枢点
int incr1 = 0, incr2 = 0;
while (true) {
//nums1中所有元素均被排除,第k个元素只能存在于数组nums2中
if (p1 == nums1.length) {
return nums2[p2 + k - 1];
} else if (p2 == nums2.length) {
return nums1[p1 + k - 1];
} else if (k == 1) {
//两个数组nums1和nums2中找到第1个元素,只需要比较数组头部的元素
return Math.min(nums1[p1], nums2[p2]);
}
//指针偏移的增量不能使得指针超过数组的长度
incr1 = Math.min(k / 2 - 1, nums1.length - 1 - p1);
incr2 = Math.min(k / 2 - 1, nums2.length - 1 - p2);
if (nums1[p1 + incr1] < nums2[p2 + incr2]) {
//p1指针从判断的轴枢点,向后移动一个单位,nums1[p1..p1+incr1]中不包含第k个元素
p1 += (incr1 + 1);
k -= (incr1 + 1);
} else {
p2 += (incr2 + 1);
k -= (incr2 + 1);
}
}
}
public static void main(String[] args) {
System.out.println(findMedianSortedArrays(new int[] { 1, 3 }, new int[] { 2 }));
// System.out.println(findKthElement(new int[] { }, new int[] { }, 1));
}
}