LeetCode 4.寻找两个正序数组的中位数

博客围绕求两个正序数组中位数展开。介绍两种思路,一是合并数组后取中位数,时间复杂度为遍历两数组时间,空间复杂度因开辟新数组而定;二是用二分查找转化为求第k小数,每次减少一半查找量,时间复杂度O(log(m+n)),空间复杂度O(1)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

给定两个正序数组(数值从小到大排列)nums1nums2,求出并返回两个数组的中位数

思路:

总的来说分两种思路:

  1. 第一种简单易想,就是将两个数组按照正序合并成一个新的数组,之后根据索引返回它的中位数。时间复杂度为O(m+n),也就是遍历两个数组的时间。空间复杂度为O(m+n):开辟了一个新的数组

以下为第一种方法代码+注释:比较简单,本文主要说明第二种

方法一:暴力解法:时间复杂度O(m+n)
	public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 1.先将两数组合并
        int[] help = new int[nums1.length + nums2.length];
        int p1 = 0, p2 = 0, cur = 0;
        while(p1 < nums1.length && p2 < nums2.length){
            help[cur++] = nums1[p1] > nums2[p2] ? nums2[p2++] : nums1[p1++];
        }
        while(p1 < nums1.length){
            help[cur++] = nums1[p1++];
        }
        while(p2 < nums2.length){
            help[cur++] = nums2[p2++];
        }
        int len = nums1.length + nums2.length;
        double res = 0.0;
        // 如果为奇数个,就返回正中间那个
        if((len & 1) == 1){
            res = help[len / 2];
        }else{ 
            res = (help[len / 2 - 1] + help[len / 2]) / 2.0;
        }
        // 2.返回中位数
        return res;
	}

  1. 第二种:既然是查找中位数,那我们可以使用二分查找来解决这道题。也就可以将该题转化为求第k小数

第二种方法主要思路:

  • 先根据数组总长度找到中位数的位置,也就是我们要求的第k个数
  • 分到每个数组中索引值就变成了k/2-1(因为索引是从0开始的所以要-1),让当前时刻两个数组中k/2-1位置的值进行比较,小的那一个数组前面k/2个数一定都不满足,这样一次就砍掉了一半需要查找的数,直到最后k==1的时候,返回两个数组中当前位置第一个值较小的那一个Math.min(nums1[start2],nums2[start2]
  • 特殊情况:有可能出现一个数组长度比k/2还要小的情况,这时候直接根据索引在另一个数组中返回值即可

该方法时间复杂度为O(log(m+n)),因为每次都减少了一半的查找量,空间复杂度为O(1)

以下为代码+注释,结合注释看应该好理解一点:


public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 方法二:二分查找
        int len1 = nums1.length;
        int len2 = nums2.length;
        // 找到左边的值和右边的值
        // 将奇数情况和偶数情况合并
        // 奇数情况下left和right也指向的同一个值,只不过计算了两遍最后除2即可
        int left = (len1 + len2 + 1) / 2;
        int right = (len1 + len2 + 2) / 2;
        // 都按照偶数情况的算,算出左边的和右边的加起来除2就是最终结果,奇数情况无非重复计算两遍
        return (getKthSmall(nums1, 0, len1 - 1, nums2, 0, len2 - 1, left) + 
                getKthSmall(nums1, 0, len1 - 1, nums2, 0, len2 - 1, right)) / 2;
    }

    public double getKthSmall(int[] nums1, int start1, int end1, 
                              int[] nums2, int start2, int end2, int k){
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
        // 永远保持第一个数组长度不超过第二个数组长度
        // 这样判断长度的时候只需要判断第一个数组的长度,因为它永远是最小的
        if(len1 > len2)
            return getKthSmall(nums2, start2, end2, nums1, start1, end1, k);
        // 如果第一个数组长度为0,直接返回第二个数组剩下的元素中第k个位置的数
        // 注意要-1,因为索引从0开始的
        if(len1 == 0)
            return nums2[start2 + k - 1];
        // 临界情况,只需要找第1小的数,那么就是当前两个数组中首元素最小的那个,取出并返回
        if(k == 1)
            return Math.min(nums1[start1], nums2[start2]);
        // 找出每个数组剩余数组元素中第k/2位置处的值,索引从0开始的,所以最后要-1才对应数组中的位置
        int p1 = start1 + Math.min(len1, k / 2) - 1;
        int p2 = start2 + Math.min(len2, k / 2) - 1;
        // 如果当前位置第一个数组值大于第二个数组,那么就将第二个数组中p2索引之前的值删除
        // 因为已经肯定不在你这里面了
        // 代码实现上就是改变第二个数组的start2的索引
        if(nums1[p1] > nums2[p2]){
            return getKthSmall(nums1, start1, end1, nums2, p2 + 1, end2, k - (p2 - start2 + 1));
        }else{
            return getKthSmall(nums1, p1 + 1, end1, nums2, start2, end2, k - (p1 - start1 + 1));
        }
    }

笔者也在不断学习中,如有错误,欢迎指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值