寻找两个正序数组的中位数

寻找两个正序数组的中位数

算法理解起来挺简单的,但在一些细节处理上很容易出错,和参考的链接在细节上有所不同

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 AxBy 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的取值和计算就不一样。

可能由于偶数情况少算了一遍,因此计算时间还是偏优秀的,如果简化下代码应该也能压一下空间使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值