(这也是一道leetcode的经典题目:《LeetCode》解题笔记:004. Median of Two Sorted Arrays[H]

问题介绍

这是个超级超级经典的分治算法!!这个问题大致是说,如何在给定的两个有序数组里面找其中的中值,或者变形问题,如何在2个有序数组数组中查找Top K的值(Top K的问题可以转换成求第k个元素的问题)。这个算法在很多实际应用中都会用到,特别是在当前大数据的背景下。

我觉得下面的这个思路特别好,特别容易理解!!请按顺序看。是来自leetcode上的stellari英文答案,我整理并自己修改了一下。

预备知识

先解释下“割”

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

我们定义L = Max(LeftPart),R = Min(RightPart)

Ps. 割可以割在两个数中间,也可以割在1个数上,如果割在一个数上,那么这个数即属于左边,也属于右边。(后面讲单数组中值问题的时候会说)

比如说[2 3 5 7]这个序列,割就在3和5之间

[2 3 / 5 7]

中值就是(3+5)/2 = 4

如果[2 3 4 5 6]这个序列,割在4上,我们可以把4分成2个

[2 3 (4/4) 5 7]

中值就是(4+4)/2 = 4

这样可以保证不管中值是1个数还是2个数都能统一运算。

割和第k个元素

对于单数组,找其中的第k个元素特别好做,我们用割的思想就是:

常识1:如果在k的位置割一下,然后A[k]就是L。换言之,就是如果左侧有k个元素,A[k]属于左边部分的最大值。(都是明显的事情,这个不用解释吧!)


双数组

我们设:

\(C_i\)为第i个数组的割。

\(L_i\)为第i个数组割后的左元素.

\(R_i\)为第i个数组割后的右元素。

如何从双数组里取出第k个元素

  1. 首先\(L_i <= R_i\)是肯定的(因为数组有序,左边肯定小于右边)
  2. 如果我们让\(L_1 <= R_2\) && \(L_2 <= R_1\)
  3. 那么左半边 全小于右半边,如果左边的元素个数相加刚好等于k,那么第k个元素就是Max(L1,L2),参考上面常识1。
  4. 如果 L1>R2,说明数组1的左边元素太大(多),我们把C1减小,把C2增大。L2>R1同理,把C1增大,C2减小。

假设k=3

对于

\([1\ 4\ 7\ 9]\)

\([2\ 3\ 5]\)

设C1 = 2,那么C2 = k-C1 = 1

\([1\ 4/ 7\ 9]\)

\([2/3\ 5]\)

这时候,L1(4)>R2(3),说明C1要减小,C2要增大,C1 = 1,C2=k-C1 = 2

\([1/4\ 7\ 9]\)

\([2\ 3/5]\)

这时候,满足了\(L_1 <= R_2\) && \(L_2 <= R_1\),第3个元素就是Max(1,3) = 3。

如果对于上面的例子,把k改成4就恰好是中值。

下面具体来看特殊情况的中值问题。

双数组的奇偶

中值的关键在于,如何处理奇偶性,单数组的情况,我们已经讨论过了,那双数组的奇偶问题怎么办,m+n为奇偶处理方案都不同。

让数组恒为奇数

有没有办法让两个数组长度相加一定为奇数或偶数呢?

其实有的,虚拟加入‘#'(这个trick在manacher算法中也有应用),让数组长度恒为奇数(2n+1恒为奇数)。

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

映射关系

这有什么好处呢,为什么这么加?因为这么加完之后,每个位置可以通过/2得到原来元素的位置。

在虚拟数组里表示“割”

不仅如此,割更容易,如果割在‘#'上等于割在2个元素之间,割在数字上等于把数字划到2个部分。

奇妙的是不管哪种情况:

Li = (Ci-1)/2

Ri = Ci/2

例:

  1. 割在4/7之间‘#',C = 4,L=(4-1)/2=1 ,R=4/2=2

    刚好是4和7的原来位置!
  2. 割在3上,C = 3,L=(3-1)/2=1,R=3/2 =1,刚好都是3的位置!

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

左边:A[m+n] = Max(L1+L2)

右边:A[m+n+1] = Min(R1+R2)

Mid = (A[m+n]+A[m+n+1])/2

= (Max(L1+L2) + Min(R1+R2) )/2

至于在两个数组里找割的方案,就是上面的方案。

分治的思路

有了上面的知识后,现在的问题就是如何利用分治的思想。

怎么分?

最快的分的方案是二分,有2个数组,我们对哪个做二分呢?

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

怎么治?

也比较简单,我们之前分析了:就是比较L1,L2和R1,R2。

  • L1>R2,把C1减小,C2增大。—> C1向左二分
  • L2>R1,把C1增大,C2减小。—> C1向右二分

越界问题

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

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

  • C1 = 0 —— 数组1整体都比中值大,L1 >R2,中值在2中
  • C2 = 0 —— 数组1整体都比中值小,L1 <R2,中值在1中
  • C1 = n*2 —— 数组1整体都比中值小,L1 <R2,中位数在2中
  • C2 = m*2 —— 数组1整体都比中值大,L1 >R2,中位数在1中

考虑下面两种情况了,解决方案:

  • 如果C1 = 0 —> 那么我们缩小L1,L1 = INT_MIN,保证判断正确。
  • 如果C1 = n*2 —> 那么我们增大R1,R1 = INT_MAX,保证判断正确。

剩下两种情况解决方案类似。

代码

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
if(n > m) //保证数组1一定最短
return findMedianSortedArrays(nums2,nums1);
int L1,L2,R1,R2,c1,c2,lo = 0, hi = 2*n; //我们目前是虚拟加了'#'所以数组1是2*n+1长度
while(lo <= hi) //二分
{
c1 = (lo+hi)/2; //c1是二分的结果
c2 = m+n- c1;
L1 = (c1 == 0)?INT_MIN:nums1[(c1-1)/2]; //map to original element
R1 = (c1 == 2*n)?INT_MAX:nums1[c1/2];
L2 = (c2 == 0)?INT_MIN:nums2[(c2-1)/2];
R2 = (c2 == 2*m)?INT_MAX:nums2[c2/2]; if(L1 > R2)
hi = c1-1;
else if(L2 > R1)
lo = c1+1;
else
break;
}
return (max(L1,L2)+ min(R1,R2))/2.0;
}
};

【分步详解】两个有序数组中的中位数和Top K问题的更多相关文章

  1. 两个有序数组中的中位数以及求第k个最小数的值

    解法参考 <[分步详解]两个有序数组中的中位数和Top K问题> https://blog.csdn.net/hk2291976/article/details/51107778 里面求中 ...

  2. 两个有序数组的上中位数和第K小数问题

    哈,再介绍个操蛋的问题.当然,网上有很多解答,但是能让你完全看懂的不多,即便它的结果是正确的,可是解释上也是有问题的. 所以,为了以示正听,我也做了分析和demo,只要你愿意学习,你就一定能学会,并且 ...

  3. Coursera Algorithms week3 快速排序 练习测验: Selection in two sorted arrays(从两个有序数组中寻找第K大元素)

    题目原文 Selection in two sorted arrays. Given two sorted arrays a[] and b[], of sizes n1 and n2, respec ...

  4. 查找两个有序数组中的第K个元素(find kth smallest element in 2 sorted arrays)

    查找两个有序数组中的第K个元素 int FindKth(int a[], int b[], int k, int astart, int aend, int bstart, int bend) { ; ...

  5. 选取两个有序数组中最大的K个值,降序存入另一个数组中

    原题: 假设有两个有序的整型数组int *a1, int *a2,长度分别为m和n.试用C语言写出一个函数选取两个数组中最大的K个值(K可能大于m+n)写到int *a3中,保持a3降序,并返回a3实 ...

  6. [转载]寻找两个有序数组中的第K个数或者中位数

    http://blog.csdn.net/realxie/article/details/8078043 假设有长度分为为M和N的两个升序数组A和B,在A和B两个数组中查找第K大的数,即将A和B按升序 ...

  7. 两个有序数组中查找第K大数

    题目:两个数组A.B,长度分别为m.n,即A(m).B(n),分别是递增数组.求第K大的数字.   方法一: 简单的办法,使用Merge Sort,首先将两个数组合并,然后在枚举查找.这个算法的时间复 ...

  8. 【medium】4. Median of Two Sorted Arrays 两个有序数组中第k小的数

    There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two ...

  9. LeetCode 4——两个排序数组中的中位数

    1. 题目 2. 解答 2.1. 方法一 由于两个数组都是排好序的,因此首先可以想到的思路就是利用归并排序把两个数组合并成一个有序的长数组,然后直接取出中位数即可. class Solution: d ...

随机推荐

  1. Appium之启用手机桌面APP的多种方法

    方法一: 其实之前的随笔 Appium之连续启动多个应用(APP)中已经介绍了可以用appium下的start_activity()方法来启动一个应用,那这里就不再说明啦. 方法二: 因为有时用sta ...

  2. sockaddr与sockaddr_in

    struct sockaddr { unsigned short sa_family;     char sa_data[14]; }; 此数据结构用做bind.connect.recvfrom.se ...

  3. sun.misc.BASE64Decoder的风险

    问题描述 最近需要使用Base64上传图片,但是返现sun.misc.BASE64Decoder 为已经过期的包,此包为以前sun公司的内部包,可以下载此包,但是不利于现在Maven方式构建,可能会在 ...

  4. Mybatis 模糊查询 like【笔记】Could not set parameters for mapping

    当使用mybatis 做模糊查询时如果这样写 会报 Could not set parameters for mapping: ParameterMapping{property='keywords' ...

  5. virtualbox上硬盘安装coreos

    网址: http://www.serfdom.cn/index.php/archives/4/ http://www.360doc.com/content/14/1118/10/15077656_42 ...

  6. web api 请求结果中页面显示的json字符串与json对象结果不一致

    我在前端调用这个api的时候也是百思不得其解,明明看到页面上的结果ID是不一样的,但是在js中使用的时候,却一直有重复ID的情况 后来才发现原来是long这个类型的原因,JavaScript中Numb ...

  7. css细节复习笔记——结构与层叠

    每个合法的文档都会生成一个结构树,有了结构树元素的祖先.属性兄弟元素等等创建选择器来选择元素,这是CSS继承的核心.继承是从一个元素向后代元素传递属性值所采用的机制.面向一个元素使用哪些值时,用户代理 ...

  8. java学习笔记—国际化(41)

    国际化:internationalization即I18N. 举例: 本科高校的网站,一般的都有中文和英文两种页面风格.因此将这种根据不同用户群体显示不同的页面风格的方式称之为页面的国际化. 翻译 V ...

  9. java学习笔记—标准连接池的实现(27)

    javax.sql.DataSource. Java.sql.* DataSource 接口由驱动程序供应商实现.共有三种类型的实现: 基本实现 - 生成标准的 Connection 对象 – 一个D ...

  10. @media媒体查询

    @media媒体查询 @media screen and (min-width:640px) and (max-width:1920px){/*当屏幕尺寸大于640px时与小于1920时*/ .pub ...