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

题目

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

示例 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

示例 3
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000

示例 4
输入:nums1 = [], nums2 = [1]
输出:1.00000

示例 5
输入:nums1 = [2], nums2 = []
输出:2.00000

提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106

进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

方法

方法1:合并两个数组,找中位数

时间复杂度:
空间复杂度:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int l1 = nums1.length;
        int l2 = nums2.length;
        if(l1==0){
            if(l2%2 == 0){
                return (nums2[l2/2]+nums2[l2/2-1])/2.0;
            }
            if(l1%2 != 0){
                return nums2[l2/2];
            }
        }
        if(l2==0){
            if(l1%2 == 0){
                return (nums1[l1/2]+nums1[l1/2-1])/2.0;
            }
            if(l1%2 != 0){
                return nums1[l1/2];
            }
        }
        int l = l1+l2;
        int[] sum = new int[l];
        int i=0;
        int j=0;
        int count = 0;
        while(count<(l1+l2)){
            if(i==l1){
                while(j<l2){
                    sum[count++] = nums2[j++];
                }
                break;
            }
            if(j==l2){
                while(i<l1){
                    sum[count++] = nums1[i++];
                }
                break;
            }
            if(nums1[i]<nums2[j]){
                sum[count++] = nums1[i++];
            }else{
                sum[count++] = nums2[j++];
            }
        }
        if(l%2==0){
            return (sum[l/2]+sum[l/2-1])/2.0;
        }else{
            return sum[l/2];
        }
    }
}

方法2:不合并直接找中位数

时间复杂度:O(m+n)
空间复杂度:O(1)

Java版本:
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
       int l1 = nums1.length;
       int l2 = nums2.length;
       int l = l1+l2;
       int left = -1,right = -1;
       int index1 = 0,index2 = 0;
       for(int i=0;i<=l/2;i++){
           left = right;
            if(index1<l1 && (index2>=l2 || nums1[index1]<nums2[index2])){
                right = nums1[index1++];
            }else{
                right = nums2[index2++];
            }
       }
       if((l&1)==0){
           return (left+right)/2.0;
       }else{
           return right;
       }
    }
}
JavaScript版本:
var findMedianSortedArrays = function(nums1, nums2) {
    const len1 = nums1.length,len2 = nums2.length;
    const len = len1+len2;
    let index1 = 0,index2  = 0;
    let left = -1,right = -1;
    for(let i=0;i<=len/2;i++){
        left = right;
        if(index1<len1&&(index2>=len2||nums1[index1]<nums2[index2])){
            right = nums1[index1++];
        }else{
            right = nums2[index2++];
        }
    }
    if(len%2===0){
        return (left+right)/2;
    }
    return right;
};

二分查找法

假设长度为m的num1和长度为n的num2分别存在下标i和j之前的分割线,将分割线左边的数的个数分为总长的一半,并且分割线左边的数总小于等于右边,即可找到中位数:

  • 若总长为偶数,则分割线左边的最大值和分割线右边的最小值为中位数,
  • 若总长为奇数,则分割线左边的最大值为中位数;
    因此条件可表述为:
    偶数情况:i+j=m-i+n-j,num1[i-1]<=num2[j],num2[j-1]<=num1[i]
    奇数情况:i+j=m-i+n-j+1,num1[i-1]<=num2[j],num2[j-1]<=num1[i]

    第一个条件可变为i+j = (m+n+1)/2,只取整数,假设m<n,因此j=(m+n+1)/2-i
    第二个条件num1[i-1]<=num2[j],num2[j-1]<=num1[i],由于i增加时num1增大,num2减小,因此存在临界点使num1[i-1]<=num2[j],但nums[i]确不满足,因此num2[j-1]<=num1[i]自然成立,因此第二个条件变为找到最大的i满足num1[i-1]<=num2[j]
    综上所述,所需的条件即为:
    1)保证m<n
    2)j=(m+n+1)/2-i,只取整数
    3)找到最大的i使num1[i-1]<=num2[j]

其中,有以下论证:
论证1:为什么必须保证m<n?
m>n的话,j有可能是负的,因为j= (m+n+1)/2-i,因此i增大则j减小,i最大为m-1时,j=(n-m-1)/2,此时若m>n则j是负的
论证2:由于i增加时num1增大,num2减小
因为j= (m+n+1)/2-i,i增大则j减小,而两个数组都是递增的,所以i增大则j减小,对应的num1增大num2减小

  • 时间复杂度:O(log(min(m,n)),即对长度小的数组二分
  • 空间复杂度:O(1)
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length,n = nums2.length;
        if(m>n){
            return findMedianSortedArrays(nums2,nums1);
        }
        int left = 0,right = m;
        int max_left = 0,min_right = 0;
        while(left<=right){
            int mid_i = (left+right)/2;
            int mid_j = (m+n+1)/2-mid_i;

            int num1_i_1 = mid_i==0?Integer.MIN_VALUE: nums1[mid_i-1];
            int num1_i = mid_i==m?Integer.MAX_VALUE : nums1[mid_i];
            int num2_j_1 = mid_j==0?Integer.MIN_VALUE : nums2[mid_j-1];
            int num2_j = mid_j==n?Integer.MAX_VALUE : nums2[mid_j];
            if(num1_i_1<=num2_j){
                max_left = Math.max(num1_i_1,num2_j_1);
                min_right = Math.min(num1_i,num2_j);
                left = mid_i+1; //A[i-1]<=B[j].说明临界点在右边
            }else{
                right = mid_i-1;//A[i-1]>B[j].说明临界点在左边
            }
        }
        return (m+n)%2==0? (max_left+min_right)/2.0 : max_left;
    }
}

posted @   你也要来一颗长颈鹿吗  阅读(48)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示