-----------------------------思路-----------------------------
要求:取两数组合并后的中位数(如果合并后是偶数,取中间两个数之和;如果合并后是奇数,直接取中位数),要求时间复杂度为O(long(n+m))
暴力:合并后再取中位数,时间复杂度是O(nlogn),不考虑
优化算法:取合并后的中位数,抽象成取合并后的第 k 个数(特殊地,中位数是当k = (m + n) / 2的情况)。
接下来,我们给出求第k个数的思路:
k每次会除2,直到减为1
,此时,求两个数组中的第一个元素的最小值即为第 k 个数
接下来我们给一个例子方便理解这个思想:
① 设有 A = {1,3,5,8,11},B = {2,6,8,9,12}
A的长度为5,B的长度为5,设我们要找合并后的第5个数
② 即k = 5
③找A = {5,8,11}和B = {2,6,8,9,12}两数组合并后的第3个数,即k = 3
④ 找A = {8,11}和B = {2,6,8,9,12}两数组合并后的第2个数,即k = 2
⑤ 此时k = 1
,则找A = {8,11}和B = {6,8,9,12}两数组合并后的第1个数,即k = 1,则等同于求A[1]和B[1]的最小值,即第五个数为min(A[1],B[1]) = 6
k每次减少一半,因此时间复杂度为O(long(n+m))
注意
,在代码中我们没有真的删除这些值,而是用两个指针 i 和 j ,分别指向 A 和 B 数组中删除元素后的第一个值,例如A = {8,11}时,已经删除了前三个元素,但是实际是这样的:
只遍历 i 后的元素即可,逻辑上就实现了删除操作
逻辑删除:
A从 i 开始,B从 j 开始时
① 取A和B中的值A[k/2] 和 B[k-k/2],比较大小
Math.min(A.length,i + k / 2)
j + k - k / 2
② 如果 A[k/2] < B[k-k/2],那么要删除A中前 k / 2个数
si = i + k / 2
k = k - (si - i)
③ 如果 A[k/2] > B[k-k/2],那么要删除B中前k - k / 2个数
sj = j+ k - k / 2
k = k - (sj - j)
在找第k个数中,需要注意的有三点:
① 保证第一个数组长度较小
② 特判第一个数组为空的情况
③ 递归终止条件k == 1的情况
---------------------------------------------------解法---------------------------------------------------:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 总数为nums1.length+nums2.length
int total = nums1.length + nums2.length;
// 如果总数是偶数,求中间两个数之和除2
if(total % 2 == 0)
{
// 如果是偶数,找total / 2和total / 2 + 1之和再除2作为中位数
int left = find(nums1,0,nums2,0,total / 2);
int right = find(nums1,0,nums2,0,total / 2 + 1);
return (left + right) / 2.0;
}
// 如果总数是奇数,直接返回中位数 total / 2 + 1
else return find(nums1,0,nums2,0,total / 2 + 1);
}
// find函数用来找数组nums1从i开始和数组nums2从j开始中的第k个数
static int find(int[] nums1,int i,int[] nums2,int j,int k){
// 默认第一个数组是较短的数组
if(nums1.length - i > nums2.length - j) return find(nums2,j,nums1,i,k);
// 边界情况,当某一个数组使用完了所有元素,直接返回另一个数组第j + k - 1个数即为要找的第k个数(我们始终假设第一个数组是较短的数组)
// 当nums1已经用完,直接返回nums2中第j + k - 1个数即为要找的第k个数
if(nums1.length == i) return nums2[j + k - 1];
// 递归终止条件,当减到只有一个数时,取两数组的最小值
if(k == 1) return Math.min(nums1[i],nums2[j]);
// 为什么si要取min(nums1.length,i + k / 2)?
// 因为当i + k / 2大于nums.length时,有可能会越界
int si = Math.min(nums1.length,i + k / 2),sj = j + k - k / 2;
// 如果nums1中第k/2个数大于nums2中第k-k/2个数,
// 那么说明第k个数绝不在nums2的从j开始数k-k/2个数中
if(nums1[si - 1] > nums2[sj - 1])
{
return find(nums1,i,nums2,sj,k - (sj - j));
}
// 否则说明第k个数绝不在nums1中从i开始数k/2个数中,
// 直接删除这k/2个数,然后在剩下的数中查找k - (si - i)个数
else
{
return find(nums1,si,nums2,j,k - (si - i));
}
}
}
可能存在的问题
本题的思路不难,难在各种边界情况十分繁杂,要好好掌握逻辑删除的各指针的表达方式,以及逻辑删除后查找第k个数变成了查找第几个数。