webjs求数组的中位数‘_4. 寻找两个正序数组的中位数

本文介绍了一种在O(log(m+n))时间复杂度内找到两个正序数组nums1和nums2中位数的方法。首先讨论了数组合并的归并策略,虽然简单但不符合时间复杂度要求。接着提出了二分查找方法,分析了如何确保数组A和B的元素正确组合以找到中位数,并解释了如何通过二分查找快速确定所需位置。
摘要由CSDN通过智能技术生成
题目

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。

请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

44707156688a45c3ce164cb16f20e40a.png

题解
方法一、数组合并(归并)

在两个有序的数组中要找到某个数,估计大家第一想到的就是归并的方法。这也是归并排序的基本思想。开辟一个新的数组help,长度为两个数组的长度和。使用两个指针分别指向两个数组的开头,比较两个指针所指的元素,将比较小的元素放入到新的数组中。之后继续上述的操作,直到将所有的数都放入到help中。举一个简单的例子:将两个有序数组[1,3,5,6,7][2,4,8]合并为一个数组:

bd3b34f8bfa057acdbd0f19cd2955180.gif

本题是要求找中位数,中位数就是两个数组长度和的一半的位置。但是这里要注意长度奇数偶数的情况:

  • 如果总长度为奇数的话,那么合并后中间的那个数就是结果

  • 如果总长度为偶数的话,那合并后中间两个数的平均数就是结果

这里由于只需要找中位数,所以数组归并到中位数的位置就可以了,可以声明一个变量k作为跳出条件。

归并的方法简单易懂,时间复杂度与空间复杂度均为线性复杂度O(m+n)。并不符合题目要求。但是这种方法也是接下来方法的关键。

class Solution {    public double findMedianSortedArrays(int[] nums1, int[] nums2) {        int length = nums1.length + nums2.length;        double result = 0;        //分别进行奇数偶数处理        if(length % 2 != 0){            result = getNum(nums1,nums2,length/2);        }else{            result = getNum(nums1,nums2,length/2-1)/2 + getNum(nums1,nums2,length/2)/2;        }        return result;    }    public double getNum(int[] nums1, int[] nums2, int k){        int[] result = new int[nums1.length+nums2.length];        int i = 0, j = 0;        int cur = 0;        while(i < nums1.length && j <nums2.length && cur <= k){            if(nums1[i] < nums2[j]) result[cur++] = nums1[i++];            else result[cur++] = nums2[j++];        }        while(i < nums1.length && cur <=k) result[cur++] = nums1[i++];        while(j < nums2.length && cur <=k) result[cur++] = nums2[j++];        return result[cur-1];    }}
方法二、二分查找
基本问题分析

如何让时间复杂度降为O(log(m+n))呢?一般涉及到log的时间复杂度,我们都会想到二分查找的方法。其实这道题也不例外。我们仔细观察下面的例子:

7e189379a2b5f0186b61c6a6f9cd67be.png

题目要求找到中位数,通过计算我们知道中位数应当是4,5两个数的平均值,因为合并后数组的总长为8,那么中位数就是位置4,5两处的数的均值。上图中蓝色的分割线将数组分为了两部分,现在我们只看左部分,左部分中数组A贡献了两个数(2,4),数组B贡献了两个数(1,3)。两个数组一共贡献了4个数。这四个数正好是中位数的位置。这也就说明了数组A和数组B两个数组贡献的元素个数的和应该为数组的一半

94b8fa178a4853581ec8d070b232accd.png

但是这样还是不够的,比如说:

1477187ddcc6fb3eec046144dd37f75d.png

如图所示,上图的情况一与情况二都满足数组A和数组B两个数组贡献的元素个数的和应该为数组的一半(3+1)(0+4)。但是都不符合要求:

  • 情况一所构成的序列为:[1,2,4,8],但是中间少了3,而多了8

  • 情况二所构成的序列为:[1,3,5,6],中间少了2,4,而多了5,6

将上述问题更一般化为:

6768e9b7f2e5b1acb4595593df21e48d.png

如果不想出现上述的问题,我们需要保证L1  L2。由于题目中给的数组是有序的,故L1必然小于R1,L2必然小于R2。这样才能构成一个正确的有序的序列,而不至于出现情况一与情况二的问题。

所以我们的序列必须满足以下条件:

  • A贡献的元素数量 + B贡献的元素数量  =  要求的元素数量(本题是求中位数,故要求的为总数量的一半)

  • L1 < R2

  • L2 < R1

整个问题基本就是这个样子,思路依旧来源于之前的方法一,只是将其细化进而转换思维方式。所以解任何题目,甚至是在科研学习当中,都不应该放弃最简单的方法,可能效率并不是特别高,但是却提供了一个能让我们继续思考下去的台阶

如何快速的找到 L1 / R1 和 L2/ R2 的位置

这里就要用到二分查找了。由于A贡献的元素数量 + B贡献的元素数量  =  要求的元素数量的限制,只要找到了L1 / R1的位置,也就找到的了 L2/ R2 。为了书写方便,将在数组A中的位置表示为curA,在数组B中位置表示为curB,(本题中curA + curB = (A.length + b.length +1)/2)。

这样只需要在一个数组中进行二分查找了。我们选择长度比较短的数组作为查找数组:

//初始化二分查找的边界int L_edge = 0, R_edge = A.length;//数组A中的位置表示为curA,在数组B中位置表示为curBint curA,curB;double result = 0;while(L_edge <= R_edge){    curA = L_edge + (R_edge - L_edge)/2;    curB = (length+1)/2 - curA;    //计算出L1,R1,L2,R2    double L1 = curA == 0 ? Integer.MIN_VALUE:A[curA-1];    double R1 = curA == A.length ? Integer.MAX_VALUE:B[curA];    double L2 = curB == 0 ? Integer.MIN_VALUE:B[curB-1];    double R2 = curB == B.length ? Integer.MAX_VALUE:B[curB];    //二分查找,重新划定边界    if(L1 > R2) R_edge = curA-1;    else if( L2 > R1) L_edge = curA+1;    else{    //注意长度为奇数偶数的问题,奇数取中间的那个值,偶数则取两边的和的一半    if(length % 2 != 0) result = Math.max(L1,L2);    else result = (Math.max(L1,L2) + Math.min(R1,R2))/2.0;    break;    }}

1958324f8a60ded8ef9f0b00f0d338d7.gif

问题:为什么要选择较短的数组作为查找数组呢?

其实选择哪个数组作为查找数组都可以。选择短的那个能够使时间复杂度变为Olog(min(m,n))

代码
class Solution {    public double findMedianSortedArrays(int[] nums1, int[] nums2) {        int length = nums1.length + nums2.length;        //选择长度较小的那个数组进行查找        if(nums1.length > nums2.length) return findMedianSortedArrays(nums2,nums1);        if(nums1.length == 0){            if(nums2.length % 2 != 0) return nums2[length/2];            else return (nums2[length/2-1] + nums2[length/2])/2.0;        }        初始化二分查找的边界        int L_edge = 0, R_edge = nums1.length;        int cur1 = 0,cur2 = 0;        double result = 0;        while(L_edge <= R_edge){            cur1 = L_edge + (R_edge - L_edge)/2;            cur2 = (length+1)/2 - cur1;            //计算出L1,R1,L2,R2            double L1 = cur1 == 0 ? Integer.MIN_VALUE:nums1[cur1-1];            double R1 = cur1 == nums1.length ? Integer.MAX_VALUE:nums1[cur1];            double L2 = cur2 == 0 ? Integer.MIN_VALUE:nums2[cur2-1];            double R2 = cur2 == nums2.length ? Integer.MAX_VALUE:nums2[cur2];            //二分查找,重新划定边界            if(L1 > R2) R_edge = cur1-1;            else if( L2 > R1) L_edge = cur1+1;            else{                //注意长度为奇数偶数的问题,奇数取中间的那个值,偶数则取两边的和的一半                if(length % 2 != 0) result = Math.max(L1,L2);                else result = (Math.max(L1,L2) + Math.min(R1,R2))/2.0;                break;            }        }        return result;    }}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值