Leetcode 4. 寻找两个正序数组的中位数 (数组划分+中位数位置搜索)

题解

  • 题意:给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

  • 题解:此题主要是找到两个数组的中位数位置,不断地在两个数组中寻找合理的分割线位置。

    • 中位数位置(m+n)偶数:(m+n+1)/2的左右两边,m+n奇数:(m+n+1)/2所处元素。对于奇数来说,就是划分成两堆数据之后,奇数长度数组的中位数就在元素多一个的那边(如9个元素,划分成5+4,中位数就在5那一堆)
    • 在两个数组查找中位数:这个地方的困难在于,我们虽然知道数组内部元素的大小关系,但是并不知道不同数组中元素的大小关系。但是思路没有变,想要找到中位数,就离不开将所有元素划分成左右两堆,那么如何在两个数组中对元素进行划分成左边小右边大的情况呢?
    • 首先我们要知道分割线左右两端的元素需要满足的条件
      • 同一个数组的比较:因为是有序数组,因此在相同数组中,分割线左右两边的元素自然是满足左边元素小,右边元素大的条件的
      • 交叉比较:但是光满足上面的条件不足以划分中位数,还需要满足
        • nums1的分割线左边一定小于nums2右边的元素
        • nums2的分割线左边一定小于nums1右边的元素
      • 表示两个数组中分割线的位置:知一得二。因为中位数的位置一定处于x=(m+n+1)/2附近,因此,两个数组的分割线位置相加就应该是x的位置,我们使用一个元素i记录在nums1中分割线的位置,j记录在nums2中分割线的位置,那么j=x-i
    • 那么要如何找到合适的分割线位置呢
      • 因为有知一得二的性质,我们可以通过在nums1中分割线,定位nums2中的分割线,那么我们只需要在nums1中利用二分查找找到合适的分割线即可。
      • 每次更新分割线,就让nums1[i-1]nums2[j]进行比较,也就是nums1分割线左边第一个元素和nums2分割线右边第一个元素的比较。
        • 如果满足条件,说明当前元素满足左堆元素的要求,就加入到左堆中,然后从[i,right]继续找
        • 如果不满足条件,则说明当前元素太大, 要将范围缩小为[left, i-1]
        • 注意,这里甚至都不需要使用nums2中的左边元素与nums1中的右边元素进行对比,nums1分割线满足条件之后,nums2一定也会满足,这两个是充要条件。
    • 边界条件:要注意,当一个数组里面的元素完全小于另一个数组的元素的时候,那么此时会出现i==m或者i==0的情况,那么此时对应的nums[i]nums[i-1]会出现越界的情况,因此实现的时候要注意,对于第一种情况,我们使用左边元素i-1进行比较,因此nums[i-1]不会出现越界,对于第二种情况,二分使用(right+left+1)/2可以避免,因为存在两个元素的时候,永远选择的都是右边的。但是最后的时候还是需要进行一个特判,因为此时是需要考虑右边的元素(偶数长度数组的中位数),此外,虽然二分查找的时候i不会取到0,但是可能直接不进入二分查找的过程,比如第一个数组是空的,此时i=left=0
  • 实现: 二分查找长度短的数组可以提高效率

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if(nums1.length > nums2.length){
            int []tmp = nums1;
            nums1 = nums2;
            nums2 = tmp;
        }
        int m = nums1.length, n = nums2.length;
        int left = 0, right = m;
        int mid = (m+n+1)/2;
        while(left < right){
            int i = left + (right-left+1)/2;
            int j = mid-i;
            if(nums1[i-1] > nums2[j]){
                right = i-1;
            }
            else{
                left = i;
            }
        }
        // 边界处理, i是分割线, 但是要注意i==0 和 i==m的情况
        int i = left;
        int j = mid - i;
        int nums1left = (i==0)?Integer.MIN_VALUE:nums1[i-1];
        int nums1right = (i==m)?Integer.MAX_VALUE:nums1[i];
        
        int nums2left = (j==0)?Integer.MIN_VALUE:nums2[j-1];
        int nums2right = (j==n)?Integer.MAX_VALUE:nums2[j];
        
        if((m+n)%2 == 1) return Math.max(nums1left, nums2left);
        else return (double)(Math.max(nums1left, nums2left) + Math.min(nums1right, nums2right))/2;
        
    }
}

题目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值