寻找两个正序数组的中位数
算法理解起来挺简单的,但在一些细节处理上很容易出错,和参考的链接在细节上有所不同
https://blog.csdn.net/en_joker/article/details/107179641
核心:去除比中位数小的所有数
如果用O(m+n)的解法,就是比较两个数组的第一个数,小的那个删除一个数,删掉一定个数后,中位数就暴露在最前面了
这里使用O(log(m+n))的解法,其实就是将需要相比的位置使用二分来查找。
主要思路
设数组分别为A和B,两个数组的总长度为length,k>=1
如果length=2k,则需要删除k-1个数,第k,k+1个数为中位数
如果length=2k-1,则需要删除k-1个数,第k个数为中位数
设k=(length+1)/2
当k-1=0,即k=1时,停止删除,每次删除后k需要减掉删除的个数
问题是每次删除多少个数是安全的。
A x A_x Ax表示A的第x个数, A B k AB_k ABk表示两个数组有序合并后第k个数
每次A和B取 A x A_x Ax和 B y B_y By来比较,如果 A x ≤ B y A_x\leq B_y Ax≤By将 A 1 A_1 A1~ A x A_x Ax的数都删除,反之删除B的
讨论点: A k 2 A_\frac k 2 A2k或 B k 2 B_\frac k 2 B2k可能为中位数,如何证明中位数不会被删除
设 A 1 A_1 A1~ A k 2 A_\frac k 2 A2k和 B 1 B_1 B1~ B k 2 B_\frac k 2 B2k的两者的集合为U,元素数为T=k/2*2<=k,。
当k/2超过A或B的个数时,集合U的元素数T只会<k,即T<=k依然成立。
设中位数 A B k AB_k ABk为 A k 2 A_\frac k2 A2k。
当 A k 2 A_\frac k2 A2k被删除时,至少有一个数大于 A k 2 A_\frac k2 A2k且和中位数不在同一个数组中,即 B k 2 + n B_{\frac k2+n} B2k+n> A k 2 A_\frac k2 A2k
由于所有小于 B k 2 + n B_{\frac k2+n} B2k+n和 A k 2 A_\frac k2 A2k的数都需要在集合U中,其个数为k+n>T,无法成立
因此 B k 2 + n B_{\frac k2+n} B2k+n不存在,即中位数 A k 2 A_\frac k2 A2k不可能被删除
经上述证明,我们可以放心比较 A k 2 A_\frac k2 A2k和 B k 2 B_\frac k2 B2k,不用担心中位数会被删除,即每次最多删除k/2个数,某个数组越界时根据数组剩余数量决定。
length为奇数和偶数时的不同处理
当k==1时,停止删除,此时需要取出中位数
- 当length为奇数时,中位数为第1个小的数,选择两个数组的首个数中的较小数
- 当length为偶数时,中位数为第1个和第2个小的数,对两个数组做两次取较小数即可
- 当某个数组为空时,从另一个数组中取数。
最后输出中位数即可
代码
public class Solution
{
public double FindMedianSortedArrays(int[] nums1, int[] nums2)
{
bool isOushu = (nums1.Length + nums2.Length) % 2 == 0;
int k = (nums1.Length + nums2.Length+1) /2 ;
int start1 = 0, start2 = 0;
while (k > 1)
{
//如果某个数组为空则直接返回答案
if (start1 >= nums1.Length)
return isOushu ? ((nums2[start2 + k - 1] + nums2[start2 + k]) / 2.0) : nums2[start2 + k - 1];
else if (start2 >= nums2.Length)
return isOushu ? ((nums1[start1 + k - 1] + nums1[start1 + k]) / 2.0) : nums1[start1 + k - 1];
//偏移量,偏移0表示拿第一个进行比较
int dev1 = Math.Min( k / 2 , nums1.Length - start1)-1;
int dev2 = Math.Min(k / 2, nums2.Length - start2)-1;
if (nums1[dev1 + start1] <= nums2[dev2 + start2])
{
//因为dev最小为0所以k需要多减1
k -= dev1+1;
start1 += dev1+1;
}
else
{
k -= dev2+1;
start2 += dev2+1;
}
}
if (isOushu)
return oushuMin(nums1,nums2,start1,start2);
else
return jishuMin(nums1, nums2, ref start1, ref start2);
}
public double oushuMin(int[] nums1, int[] nums2,int start1,int start2)
{
//偶数情况就是取两次最小
int n1 = jishuMin(nums1, nums2,ref start1,ref start2);
int n2 = jishuMin(nums1, nums2,ref start1,ref start2);
return (n1 + n2) / 2.0;
}
public int jishuMin(int[] nums1,int[] nums2,ref int start1, ref int start2)
{
if (start1>=nums1.Length) return nums2[start2++];
if (start2>=nums2.Length) return nums1[start1++];
if (nums1[start1] >= nums2[start2])
return nums2[start2++];
else
return nums1[start1++];
}
}
和链接里答案的不同
由于只看了他的思路,没有抄代码跑,所以一开始照他的思路结合自己的思路写出来的代码一直无法通过。
仔细比较了下,发现他在偶数情况下会跑两遍主要计算的代码,个人感觉大大浪费了时间,因此也导致我加了自己的想法后就无法通过了
因此重新理了一遍算法,按照自己的思路思考一遍后写的,比如有关k的取值和计算就不一样。
可能由于偶数情况少算了一遍,因此计算时间还是偏优秀的,如果简化下代码应该也能压一下空间使用。