LeetCode 04题 - 寻找两个正序数组的中位数

LeetCode 04题 -> 寻找两个正序数组的中位数

1. 题目描述
  • 寻找两个正序数组的中位数

  • 给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为 O(log(m+n))

  • 示例 1:

    输入:nums1 = [1,3], nums2 = [2]
    输出:2.00000
    解释:合并数组 = [1,2,3] ,中位数 2

  • 示例 2:
    输入:nums1 = [1,2], nums2 = [3,4]
    输出:2.50000
    解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

2. 解题思路
  • 思路1:暴力合并数组,但是不满足时间复杂度要求O(log(m+n)),过程不详细解析了,见代码

    public static double findMedianSortedArraysS1(int[] nums1, int[] nums2) {
        int[] nums = new int[m+n];
        int count = 0;
        int i = 0;
        int j = 0;
        while (i < m && j < n) {
            if(nums1[i] < nums2[j]) {
                nums[count++] = nums1[i];
                i++;
            } else if (nums1[i] > nums2[j]) {
                nums[count++] = nums2[j];
                j++;
            } else {
                nums[count++] = nums1[i];
                nums[count++] = nums2[j];
                i++;
                j++;
            }
        }
        if(i <= m-1) {
            for(int k = i; k < m; k++) {
                nums[count++] = nums1[k];
            }
        }
        if(j <= n-1) {
            for(int k = j; k < n; k++) {
                nums[count++] = nums2[k];
            }
        }
        double result;
        if(nums.length % 2 == 0) {
            result = (nums[nums.length/2] + nums[nums.length/2-1]) / 2.0;
        } else {
            result = nums[nums.length / 2];
        }
        return result;
    }
    
  • 思路2:借助二分查找

    根据时间复杂度要求O(log(m+n)),其实就是在暗示需要用二分查找。

    • 思路如下:
      1. 因为两个数组都是正序的,因此通过分别分割两个数组来寻找中位数,中位数肯定是两个数组的分割线左右的四个数中;

      2. 分割线需要满足的条件:

        ①左侧元素的个数 = 右侧元素的个数( + 1);默认如果 m + n是奇数,分割的时候让左边多一个数

        ②左侧元素的数值 <= 右侧元素的数值

      3. 只需要盯着一个数组来考虑分割线位置,技巧,盯着较短的数组来找分割线位置,即只要找到nums1分割后左侧元素的个数,用 (m + n + 1) /2 减掉就得到nums2分割后左侧元素的个数;

      4. 此时,就可以得到分割线左右四个元素,这里注意要判断分割线的条件②,即交叉比较是否 nums1Left <=nums2Right && nums2Left <= nums1Right;

      5. 如果不满足 nums1Left <=nums2Right,说明分割线太右边了,需要考虑左边那一半;

      6. 如果不满足nums2Left <= nums1Right,说明分割线太左边了,需要考虑右边那一半;

      7. 注意要处理边界问题。

    public static double findMedianSortedArraysS2(int[] nums1, int[] nums2) {
        if(nums1.length > nums2.length) {
            int[] temp = nums1;
            nums1 = nums2;
            nums2 = temp;
        }
        int m = nums1.length;
        int n = nums2.length;   
        //如果有一个数组为空 那肯定是nums1
        if(m == 0) {
            if((m + n) % 2 == 1) {
                return nums2[n/2];
            } else {
                return (nums2[n/2] + nums2[n/2-1]) / 2.0;
            }
        }
        int left = 0;
        int right = m;
        while (left <= right) {
            int i = (left + right + 1) / 2;
            int j = (m + n + 1) / 2 - i;
            if(j != 0 && i != m && nums2[j-1] > nums1[i]) {
                left = i + 1;
            }else if(i != 0 && j !=n && nums1[i-1] > nums2[j]) {
                right = i - 1;
            } else {
                int maxLeft;
                if(i == 0) {
                    maxLeft = nums2[j-1];
                }else if(j == 0) {
                    maxLeft = nums1[i-1];
                }else {
                    maxLeft = Math.max(nums1[i-1], nums2[j-1]);
                }
                int minRight;
                if(i == m) {
                    minRight = nums2[j];
                }else if(j == n) {
                    minRight = nums1[i];
                }else {
                    minRight = Math.min(nums1[i], nums2[j]);
                }
    
                if((m+n) % 2 == 1) {
                    return maxLeft;
                } else {
                    return (maxLeft + minRight) / 2.0;
                }
            }
        }
        return 0.0;
    }
    
  • 思路3:借助第K小数的思想

    利用第k小数的思想+递归二分法

    • 思路如下:

      ① 还是数组总长度奇偶数的情况,注意找的是第K个小数,K是比数组索引多了1的。

      ​ a.奇数,找第 (m+n)/2 + 1,即k = (m+n)/2 + 1

      ​ b.偶数,找第(m+n)/2和第(m+n)/2 + 1,即要找两次k;

      ②有两种情况:比较nums1[k/2-1]和nums2[k/2-1]

      ​ a. nums1[k/2-1] <= nums2[k/2-1]:说明比nums1[k/2-1]小的数最多只有nums1中的前k/2-1个数和nums2中前k/2-1个数,即比nums1[k/2-1]小的最多只有k-2个数,因此可以排除[nums1[0],nums1[k/2-1]];

      ​ b.nums1[k/2-1] > nums2[k/2-1]:可以排除[nums2[0],nums2[k/2-1]];

      ③有三种特殊情况需要处理:

      ​ a.有一个数组为空;

      ​ b.k==1;

      ​ c.数组越界。

public static double findMedianSortedArraysS4(int[] nums1, int[] nums2) {
    int m = nums1.length;
    int n = nums2.length;
    if((m + n) % 2 == 1) {
        return findKthMinElement(nums1, nums2, (m+n)/2 + 1);
    }else {
        return (findKthMinElement(nums1,nums2,(m+n)/2) + findKthMinElement(nums1,nums2,(m+n)/2+1)) / 2.0;
    }
}

/**
 * 获取两个正序数组中第k小数
 * @param nums1
 * @param nums2
 * @param k
 * @return
 */
public static int findKthMinElement(int[] nums1, int[] nums2, int k) {
    int m = nums1.length;
    int n = nums2.length;
    int i = 0;
    int j = 0;
    while (true) {
        //特殊情况1:有一个数组为空
        if(i == m) {
            return nums2[j + k -1];
        }
        if(j == n) {
            return nums1[i + k - 1];
        }
        //特殊情况2:k=1
        if(k == 1) {
            return Math.min(nums1[i], nums2[j]);
        }
        //正常情况开始
        int half = k/2;
        //特殊情况2:处理数组越界
        int newI = Math.min(i+half-1,m-1);
        int newJ = Math.min(j+half-1,n-1);
        if(nums1[newI] <= nums2[newJ]) {
            k -= newI - i + 1;
            i = newI + 1;
        } else {
            k -= newJ - j + 1;
            j = newJ + 1;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值