小知识

INT_MIN在标准头文件limits.h中定义。

#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX - 1)

题解思路

其实是类似的二分,优点在于通过添加 '#' ,实现更方便的二分。

题目:
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

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

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

示例 1:

nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:

nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5

算法:
为了解决这个问题,我们需要理解 “中位数的作用是什么”。在统计中,中位数被用来:

将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。

这其中又分为偶数组和奇数组:

奇数组: [2 3 5] 对应的中位数为3

偶数组: [1 4 7 9] 对应的中位数为 (4 + 7) /2 = 5.5

先解释下“割”
我们通过切一刀,能够把有序数组分成左右两个部分,切的那一刀就被称为割(Cut),割(Cut)的左右会有两个元素,分别是左边最大值和右边最小值。

我们定义LMax= Max(LeftPart),RMin = Min(RightPart)。

割可以割在两个数中间,也可以割在1个数上,如果割在一个数上,那么这个数即属于左边,也属于右边

奇数组: [2 3 5] 对应的中位数为3,假定割(Cut)在3上,我们可以把3分为2个: [2 (3/3) 5]

因此LMax=3, RMin=3

偶数组: [1 4 7 9] 对应的中位数为 (4 + 7) /2 = 5.5,假定割(Cut)在4和7之间: [1 (4/7) 9]

因此LMax=4, RMin=7

割和第k个元素
一个数组
对于一个有序数组,对于数组A,如果在k的位置割(Cut)一下(不是割(Cut)在两数中间),那么 LMax = RMin = A[k],

两个数组
也就是我们题目的状态,我们要求得两个数组合并成一个有序数组时,第k位的元素

我们设:
Ci为第i个数组的割。

LMaxi为第i个数组割后的左元素.

RMini为第i个数组割后的右元素。

首先,LMax1<=RMin1,LMax2<=RMin2 这是肯定的,因为数组是有序的,左边肯定小于右边!,而如果割(Cut)在某个数上,则左右相等。

其次,如果我们让LMax1<=RMin2,LMax2<=RMin1 呢

那么如果左半边全小于右半边,如果左边的元素个数相加刚好等于k, 那么第k个元素就是Max(LMax1, LMax2),这个比较好理解的,因为Max(LMax1, LMax2)肯定是左边k个元素的最大值,因为合并后的数组是有序,第k个元素肯定前面k个元素中最大的那个。

那么如果 LMax1>RMin2,说明数组1的左边元素太大(多),我们把C1减小,C2=k-C1也就相应的增大。LMax2>RMin1同理,把C2减小,C1=k-C2也就相应的增大。

假设k=3

对于

[2 3 5]

[1 4 7 9]
设C1 = 1, 那么C2 = k - C1 = 2

[2 / 3 5]

[1 4 / 7 9]

这时LMax1 =2, RMin1 = 3, LMax2=4, RMin2=7,

从而有LMax2 > RMin1,依据前面的推论,我们要将C1增大,所以我们让C1 = 2,如下:

[2 3 /5]

[1 / 4 7 9]

这时LMax1 =3, RMin1 = 5, LMax2=1, RMin2=4, 满足 LMax1 < RMin2 且 LMax2 < RMin1 , 所以第3个元素为Max(LMax1,LMax2) = 3

两个数组的最大问题是,它们合并后,m+n总数可能为奇, 也可能为偶,所以我们得想法让m+n总是为偶数

通过虚拟加入‘#’,我们让m转换成2m+1 ,n转换成2n+1, 两数之和就变成了2m+2n+2,恒为偶数。

注意是虚拟加,其实根本没这一步,通过下面的转换,我们可以保证虚拟加后每个元素跟原来的元素一一对应

这么虚拟加后,每个位置可以通过/2得到原来元素的位置:

比如 2,原来在0位,现在是1位,1/2=0

比如 3,原来在1位,现在是3位,3/2=1

比如 5,原来在2位,现在是5位,5/2=2

比如 9,原来在3位,现在是7位,7/2=3

而对于割(Cut),如果割在‘#’上等于割在2个元素之间,割在数字上等于把数字划到2个部分,总是有以下成立:

LMaxi = (Ci-1)/2 位置上的元素
RMini = Ci/2 位置上的元素

例如:

割在3上,C = 3,LMax=a[(3-1)/2]=A[1],RMin=a[3/2] =A[1],刚好都是3的位置!

割在4/7之间‘#’,C = 4,LMax=A[(4-1)/2]=A[1]=4 ,RMin=A[4/2]=A[2]=7

剩下的事情就好办了,把2个数组看做一个虚拟的数组A,A有2m+2n+2个元素,割在m+n+1处,所以我们只需找到m+n+1位置的元素和m+n+2位置的元素就行了。(如3+4,16,割在8)

左边:A[m+n+1] = Max(LMax1,LMax2)

右边:A[m+n+2] = Min(RMin1,RMin2)

==>Mid = (A[m+n+1]+A[m+n+2])/2 = (Max(LMax1,LMax2) + Min(RMin1,RMin2) )/2

最快的割(Cut)是使用二分法,

有2个数组,我们对哪个做二分呢?
根据之前的分析,我们知道了,只要C1或C2确定,另外一个也就确定了。这里,为了效率,我们肯定是选长度较短的做二分,假设为C1。

LMax1>RMin2,把C1减小,C2增大。—> C1向左二分

LMax2>RMin1,把C1增大,C2减小。—> C1向右二分

如果C1或C2已经到头了怎么办?

这种情况出现在:如果有个数组完全小于或大于中值。假定n<m, 可能有4种情况:

C1 = 0 —— 数组1整体都在右边了,所以都比中值大,中值在数组2中,简单的说就是数组1割后的左边是空了,所以我们可以假定LMax1 = INT_MIN

C1 =2n —— 数组1整体都在左边了,所以都比中值小,中值在数组2中 ,简单的说就是数组1割后的右边是空了,所以我们可以假定RMin1= INT_MAX,来保证LMax2<RMin1恒成立

C2 = 0 —— 数组2整体在右边了,所以都比中值大,中值在数组1中 ,简单的说就是数组2割后的左边是空了,所以我们可以假定LMax2 = INT_MIN

C2 = 2m —— 数组2整体在左边了,所以都比中值小,中值在数组1中, 简单的说就是数组2割后的右边是空了,为了让LMax1 < RMin2 恒成立,我们可以假定RMin2 = INT_MAX

#include <stdio.h>
#include <vector>
using namespace std; #define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b)) class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size(); if (n > m) //保证数组1一定最短
{
return findMedianSortedArrays(nums2, nums1);
} // Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
int LMax1, LMax2, RMin1, RMin2, c1, c2, lo = 0, hi = 2 * n; //我们目前是虚拟加了'#'所以数组1是2*n长度,下标0-2n,(2N+1) while (lo <= hi) //二分
{
c1 = (lo + hi) / 2; //c1是二分的结果 在2n+1内部割一次,左边
c2 = m + n - c1; //2m+1内部割,割的数量为m+n+1-c1 //一共割m+n+1,(由于下标0-2m+2+1,故m+n-c1)不是在整体中2m+2m+2割,分别在两个2m+2和2n+2中割,加起来m+n+1
//想象两个数组割成四部分,前两部分合并为总数组的第一部分
LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2]; if (LMax1 > RMin2)
hi = c1 - 1;
else if (LMax2 > RMin1)
lo = c1 + 1;
else
break;
}
return (max(LMax1, LMax2) + min(RMin1, RMin2)) / 2.0;
}
}; int main(int argc, char *argv[])
{
vector<int> nums1 = { 2,3, 5 };
vector<int> nums2 = { 1,4,7, 9 }; Solution solution;
double ret = solution.findMedianSortedArrays(nums1, nums2);
return 0;
}

作者:bian-bian-xiong
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/4-xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-shu/

my思路

/* 差不多的二分,但是由于没有加#处理导致细节处理麻烦。。。*/

这道题似乎限制了数组n,m均为int数组。

但是又没提,感觉怪怪的

假定两个有序数组长度分别为M,n(有序很重要)

题目要求时间复杂度为 O(log(m + n)),那么显然是m+n然后二分。关键在于如何二分。

想象两数组合并,最后的中位数必然左右各(M+N)/2个数。(先不考虑奇偶)

令较长的数组list1在前(这样中位数mid必然落在此数组数据范围内,否则mid<list1min或mid>list1max,左右两边数字数目不均等,不符合中位数定义。或者两数组恰好元素数量相等,中位数恰好在list1max与list2min之间)

设两数组为list1,list2,长度为m,n

将两个数组以下标cut1,cut2分别切开,左右分别为LMax1,RMin1,LMAX2, RMin2

LMAX1为list1[list1cut-1],RMIN1为list1[list1cut],LMAX2为list2[list2cut-1],RMIN2为list2[list2cut]

,(比如list[1,2,3,4],list1cut=2,LMAX1=2,RMIN1=3)

显然LMAX1<=RMIN1,LMAX2<=RMIN2

当满足LMAX1<=RMAX2,LMAX2<=RMIN1时,可求得中位数mid =(max(LMAX1,LMAX2)+min(RMIN1,RMIN2))/2

若LMAX1>RMAX2,  r = list1cut-1

若LMAX2>RMAX1,  l = list1cut+1

二分查找初始条件为l=0,r=M+N,

list1cut=(l+r)/2,

list2cut=(m+n)/2-list1cut;

这样list1贡献list1cut个数,lsit2贡献list2cut个数,相加共(m+n)/2个数,刚好为中位数左边的数。

现在考虑奇偶。

当m+n为偶数,(M+N)/2为int,按照如上所述处理即可。

其中LMAX1为list1[list1cut-1],RMIN1为list1[list1cut],LMAX2为list2[list2cut-1],RMIN2为list2[list2cut]

当m+n为奇数,中位数其实必然是m数组或n数组中某个数。二分时令list1和list2选出来的数个数加起来为(m+n+1)/2,当满足LMAX1<=RMIN2,LMAX2<=RMIN1时,mid=min(RMIN1,RMIN2)

初始化 r 为m+n

list1cut=(l+r)/2,

list2cut=(m+n)/2-list1cut;

然后上述思路的细节处理在于边界处理,比如listcut=0,或m,n这种情况时。处理方法为利用INT_MAX,MIN当不存在时规定。

然后跑了一次错了

例子为

[]
[1]

这才发现读题读错了。。。是不同时为空。加一个处理即可。

leetcode 4 寻找两个有序数组的中位数 二分法&INT_MAX的更多相关文章

  1. Java实现 LeetCode 4 寻找两个有序数组的中位数

    寻找两个有序数组的中位数 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2. 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n)). 你可以假设 n ...

  2. 【LeetCode】寻找两个有序数组的中位数【性质分析+二分】

    给定两个大小为 m 和 n 的有序数组 nums1 和 nums2. 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n)). 你可以假设 nums1 和 nums2 ...

  3. [LeetCode] 4. 寻找两个有序数组的中位数

    题目链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/ 题目描述: 给定两个大小为 m 和 n 的有序数组 nums1 和 ...

  4. 【LeetCode】寻找两个有序数组的中位数

    给定两个大小为 m 和 n 的有序数组 nums1 和 nums2. 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n)). 你可以假设 nums1 和 nums2  ...

  5. leetcode 4寻找两个有序数组的中位数

    最优解O(log(min(m,n))) /** 之前用合并有序数组的思想做了O((m+n+1)/2),现在试一试O(log(min(m,n))) 基本思路为:通过二分查找较小的数组得到对应的中位数(假 ...

  6. LeetCode Golang 4. 寻找两个有序数组的中位数

    4. 寻找两个有序数组的中位数 很明显我偷了懒, 没有给出正确的算法,因为官方的解法需要时间仔细看一下... func findMedianSortedArrays(nums1 []int, nums ...

  7. Leetcode(4)寻找两个有序数组的中位数

    Leetcode(4)寻找两个有序数组的中位数 [题目表述]: 给定两个大小为 m 和 n 的有序数组 nums1 和* nums2. 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O( ...

  8. 0004. 寻找两个有序数组的中位数(Java)

    4. 寻找两个有序数组的中位数 https://leetcode-cn.com/problems/median-of-two-sorted-arrays/ 最简单的就是用最简单的,把两个数组分别抽出然 ...

  9. leetcode题目4.寻找两个有序数组的中位数(困难)

    题目描述: 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2. 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n)). 你可以假设 nums1 和  ...

随机推荐

  1. Trino总结

    文章目录 1.Trino与Spark SQL的区别分析 2.Trino与Spark SQL解析过程对比 3.Trino基本概念 4.Trino架构 5.Trino SQL执行流程 6.Trino Ta ...

  2. SQL Server 日志收缩方法

    在日常运维中,有时会遇到"The transaction log for database 'xxxx' is full due to 'ACTIVE_TRANSACTION'." ...

  3. 阿里云镜像仓库镜像迁移至私有Harbor

    下载镜像同步工具 wget https://goodrain-delivery.oss-cn-hangzhou.aliyuncs.com/boe/image-syncer && chm ...

  4. GlusterFS数据存储脑裂修复方案最全解析

    本文档介绍了glusterfs中可用于监视复制卷状态的heal info命令以及解决脑裂的方法 一. 概念解析 常见术语 名称 解释 Brick GlusterFS 的基本存储单元,由可信存储池中服务 ...

  5. thinkphp如何实现伪静态

    去掉 URL 中的 index.php ThinkPHP 作为 PHP 框架,是单一入口的,那么其原始的 URL 便不是那么友好.但 ThinkPHP 提供了各种机制来定制需要的 URL 格式,配合 ...

  6. MySQL之谓词下推

    MySQL之谓词下推 什么是谓词 在SQL中,谓词就是返回boolean值即true或者false的函数,或是隐式转换为boolean的函数.SQL中的谓词主要有 LKIE.BETWEEN.IS NU ...

  7. 在Sublime Text 2工具下编辑laravel框架

    介绍Sublime编辑器 Sublime Text 3官方版是Sublime Text2的升级版.Sublime Text是一款流行的文本编辑器软件,有点类似于TextMate,跨平台,可运行在Lin ...

  8. 内联扩展 inline expansion An Inline Function is As Fast As a Macro 与宏的比较

    让编译器直接将完整的函数体插入到每一个调用该函数的地方,从而提高函数调用的运行速度. 优秀的JIT编译器会通过侦测运行信息,仅将需要频繁运行的瓶颈部分进行编译,从而大大削减编译所需的时间. 而且,利用 ...

  9. pycharm设置头文件模板(for mac)

    我们要达到的效果是每次新建一个.py文件都会有头文件,关于头文件的作用之前已做解释

  10. Fiddler扩展——自定义列数据&Tunnel to 443解决办法

    在平时日常工作中,使用Fiddler的占比还是蛮大的.使用过程,也会遇到一些小问题,问题虽小,但抓不到包,分析不了问题与数据,那也是件麻烦的事情. 以前也分享过一些小技巧,可以找以前的博文查看,具体地 ...