[LeetCode] 4. 寻找两个正序数组的中位数(java实现)

1. 题目

在这里插入图片描述

2. 读题(需要重点注意的东西)

-----------------------------思路-----------------------------
要求:取两数组合并后的中位数(如果合并后是偶数,取中间两个数之和;如果合并后是奇数,直接取中位数),要求时间复杂度为O(long(n+m))
暴力:合并后再取中位数,时间复杂度是O(nlogn),不考虑
优化算法:取合并后的中位数,抽象成取合并后的第 k 个数(特殊地,中位数是当k = (m + n) / 2的情况)。
接下来,我们给出求第k个数的思路
在这里插入图片描述
k每次会除2,直到减为1,此时,求两个数组中的第一个元素的最小值即为第 k 个数

接下来我们给一个例子方便理解这个思想

① 设有 A = {1,3,5,8,11},B = {2,6,8,9,12}
A的长度为5,B的长度为5,设我们要找合并后的第5个数
② 即k = 5

  • 首先取A数组的第 5 / 2 = 2 个数,取B数组中的第 k - 2 = 3
  • 判断A[2](数组下标从1开始) 和 B[3]的大小,A[2] = 3,B[3] = 8,则 A[2] < B[3],说明第k个数一定不在A的前2个数中
  • 直接删除A中的前两个数,则此时A = {5,8,11}
  • 那么要找的数就从第5个数,变成了在新的A和B中找第k - 2 = 3个数

③找A = {5,8,11}和B = {2,6,8,9,12}两数组合并后的第3个数,即k = 3

  • 首先取A数组的第 3 / 2 = 1 个数,取B数组中的第 k - 1 = 2个数
  • A[1] = 5,B[2] = 6,则A[1] < B[6],同理,删除A中A[1]及之前的数,因为第k个数一定不在其中
  • 此时A = {8,11},B = {2,6,8,9,12},k = 3 - 1 = 2

④ 找A = {8,11}和B = {2,6,8,9,12}两数组合并后的第2个数,即k = 2

  • 首先取A数组的第 2 / 2 = 1 个数,取B数组中的第 k - 1 = 1个数
  • A[1] = 8,B[1] = 2,A[1] > B[1],则删除B中B[1]前的数,此时B = {6,8,9,12},k = 2 - 1 = 1

此时k = 1,则找A = {8,11}和B = {6,8,9,12}两数组合并后的第1个数,即k = 1,则等同于求A[1]和B[1]的最小值,即第五个数为min(A[1],B[1]) = 6

k每次减少一半,因此时间复杂度为O(long(n+m))

注意,在代码中我们没有真的删除这些值,而是用两个指针 i 和 j ,分别指向 A 和 B 数组中删除元素后的第一个值,例如A = {8,11}时,已经删除了前三个元素,但是实际是这样的:
在这里插入图片描述
只遍历 i 后的元素即可,逻辑上就实现了删除操作


逻辑删除

A从 i 开始,B从 j 开始时
① 取A和B中的值A[k/2] 和 B[k-k/2],比较大小

  • A中取k / 2的下标为:Math.min(A.length,i + k / 2)
  • A中取k - k / 2的下标为:j + k - k / 2

② 如果 A[k/2] < B[k-k/2],那么要删除A中前 k / 2个数

  • 删除A中一定不可能的数后,i 为:si = i + k / 2
  • 删除A中一定不可能的数后,新的 k 为:k = k - (si - i)

③ 如果 A[k/2] > B[k-k/2],那么要删除B中前k - k / 2个数

  • 删除B中一定不可能的数后,j 为:sj = j+ k - k / 2
  • 删除B中一定不可能的数后,新的 k 为:k = k - (sj - j)

3. 解法

在找第k个数中,需要注意的有三点:

① 保证第一个数组长度较小
② 特判第一个数组为空的情况
③ 递归终止条件k == 1的情况

---------------------------------------------------解法---------------------------------------------------

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 总数为nums1.length+nums2.length
        int total = nums1.length + nums2.length;
        // 如果总数是偶数,求中间两个数之和除2
        if(total % 2 == 0)
        {
            // 如果是偶数,找total / 2和total / 2 + 1之和再除2作为中位数
            int left = find(nums1,0,nums2,0,total / 2);
            int right = find(nums1,0,nums2,0,total / 2 + 1);
            return (left + right) / 2.0;
        }
        // 如果总数是奇数,直接返回中位数 total / 2 + 1
        else return find(nums1,0,nums2,0,total / 2 + 1);
    }

    // find函数用来找数组nums1从i开始和数组nums2从j开始中的第k个数
    static int find(int[] nums1,int i,int[] nums2,int j,int k){
    
        // 默认第一个数组是较短的数组
        if(nums1.length - i > nums2.length - j) return find(nums2,j,nums1,i,k);
        // 边界情况,当某一个数组使用完了所有元素,直接返回另一个数组第j + k - 1个数即为要找的第k个数(我们始终假设第一个数组是较短的数组)
        // 当nums1已经用完,直接返回nums2中第j + k - 1个数即为要找的第k个数
        if(nums1.length == i) return nums2[j + k - 1];

        // 递归终止条件,当减到只有一个数时,取两数组的最小值
        if(k == 1) return Math.min(nums1[i],nums2[j]);

        // 为什么si要取min(nums1.length,i + k / 2)?
        // 因为当i + k / 2大于nums.length时,有可能会越界
        int si = Math.min(nums1.length,i + k / 2),sj = j + k - k / 2;

        // 如果nums1中第k/2个数大于nums2中第k-k/2个数,
        // 那么说明第k个数绝不在nums2的从j开始数k-k/2个数中
        if(nums1[si - 1] > nums2[sj - 1])
        {
            return find(nums1,i,nums2,sj,k - (sj - j));
        }
        // 否则说明第k个数绝不在nums1中从i开始数k/2个数中,
        // 直接删除这k/2个数,然后在剩下的数中查找k - (si - i)个数
        else
        {
            return find(nums1,si,nums2,j,k - (si - i));
        }
    }
}

可能存在的问题

4. 可能有帮助的前置习题

5. 所用到的数据结构与算法思想

6. 总结

本题的思路不难,难在各种边界情况十分繁杂,要好好掌握逻辑删除的各指针的表达方式,以及逻辑删除后查找第k个数变成了查找第几个数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cloudeeeee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值