您的位置:首页 > 其它

[LeetCode] 求两个有序数组的中位数

2017-06-09 10:33 274 查看

[LeetCode] 求两个有序数组的中位数

From: https://leetcode.com/problems/median-of-two-sorted-arrays/#/solutions

我的个人博客:http://riot-qiu.coding.me/2017/06/08/median-of-two-sorted-arrays

问题描述

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

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

例子

Example 1:

nums1 = [1, 3]
nums2 = [2]

The median is 2.0


Example 2:

nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5


Tips

Binary SearchArrayDivide and Conquer

解法

为了解决这个问题,我们首先要明白中位数的作用。在统计学中,中位数能够将一组数据划分为两个长度相同的数据集,其中一个子集总是大于另外一个子集。 如果我们能够理解在划分上的作用,那么我们离正确答案已经非常接近了。

划分AB

首先我们用一个随机位置i将A划分成两个部分:

left_A             |        right_A
A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]


假设A有m个元素,那么
i = 0 ~ m
。然后我们可以得到以下信息:

left_A.length = i, right_A.length = m - i;


PS:当
i==0
, left_A 是空的,但
i==m
, right_A 是空的。

同样我们用一个随机位置j将B划分成两部分:

left_B             |        right_B
B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]


合并AB左右子集

left_A
left_B
放在同一集合,把
right_A
right_B
放在另外一个集合,命名为
left_part
right_part
:

left_part          |        right_part
A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]
B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]


如果我们能确定下面两个条件:

1) len(left_part) == len(right_part)
2) max(left_part) <= min(right_part)


那么我们就已经把{A,B}这个集合分成了两个相同长度的部分,而且右边始终大于左边。那么中位数就等于
median = (max(left_part) + min(right_part))/2
.

PS:左边最大和右边最小之和的一半

条件限定

为了保证左右数量两边相等和左边最大小于等于右边最小,我们就需要保证:

(1) i + j == m - i + n - j (or: m - i + n - j + 1)
if n >= m, 我们只需要确定i和j的关系: i = 0 ~ m, j = (m + n + 1)/2 - i
(2) B[j-1] <= A[i] and A[i-1] <= B[j]


说明:

1. 为了过程简单,我们先假设
A[i-1],B[j-1],A[i],B[j]
这几个是一直有效的。(后面会说明怎么处理这些边界情况)

2. 为什么要
n>=m
呢?因为这是为了保证在
i
在大于等于0小于等于m的取值中,一直保持有效。因为
j=(m+n+1)/2-i
,如果
i=m
n<m
,那么
j<0
,这会得到错误的答案。

i在[0,m]中取值,找到满足
B[j-1] <= A[i] and A[i-1] <= B[j]
的情况,这个时候的i就是中间的位置。

算法过程

设置
imin = 0, imax = 0
, 然后在
[imin, imax]
这个范围里面找

设置i和j:
i = (imin + imax)/2, j = (m + n + 1)/2 - i


这样我们就保证了左边部分和右边部分已经长度相同。下面我们有三种情况需要考虑:

如果i和j满足
B[j-1] <= A[i] and A[i-1] <= B[j]
,意味当前ij已经就是我们要找的位置,停止查找。

如果
B[j-1] > A[i]
,那么意味A[i]太小了,我们需要调整i让条件
B[j-1] <= A[i]
满足。

我们可以增大i吗?

可以,因为当i增大,j势必会减小,那么对应B[j-1]会减少,A[i]会增大,经过调整
B[j-1] <= A[i]
可能刚好能够满足。

我们可以减少i吗?

不可以,与之前所述的相反,调整后条件
B[j-1] <= A[i]
依旧无法满足。

所以我们要增大i。就是调整当前的搜索范围到
[i+1, imax]
,即设置
imin = i+1
,然后回到第二步。

如果
A[i-1] > B[j]
,那么意味
A[i-1]
过大,与前一个条件相反,我们需要减少i来满足条件
A[i-1]<=B[j]
,就是调整当前的搜索范围到
[imin, i-1]
,即设置
imax = i-1
,然后回到第二步处理。

处理边界值

现在我们来考虑边界问题,当i和j等于
i=0,i=m,j=0,j=n
这四种情况的时候,
A[i-1],B[j-1],A[i],B[j]
这四个位置的值也有可能不存在。实际上,这个几种情况的处理比你想象中的要简单。

正常情况下,只要i和j不是边界,
A[i-1],B[j-1],A[i],B[j]
这四个值必定存在,我们只需要确定条件
B[j-1] <= A[i]
和条件
A[i-1] <= B[j]
。当存在
A[i-1],B[j-1],A[i],B[j]
没有值的情况,我们可能不需要确定两个条件。比如:当
i=0
A[i-1]
不存在,那么我们就不需要确定条件
A[i-1] <= B[j]
.

所以判断的条件就变成:

B[j-1] <= A[i] && A[i-1] <= B[j]  ===>
(j == 0 || i == m || B[j-1] <= A[i]) &&
(i == 0 || j == n || A[i-1] <= B[j])


而之前的循环需要判断的三个条件就变成:

1. 如果满足
(j == 0 || i == m || B[j-1] <= A[i]) && (i == 0 || j == n || A[i-1] <= B[j])
那么当前i就是要找的位置。

2. 如果
j > 0 and i < m and B[j - 1] > A[i]
,那么当前i太小。

3. 如果
i > 0 and j < n and A[i - 1] > B[j]
, 那么当前的i太大。

因为m<=n,根据i的取值范围定义,我们可以得出下面结论,所以<2>、<3>中的
j>0, j<n
可以忽略。

m <= n, i < m ==> j = (m+n+1)/2 - i > (m+n+1)/2 - m >= (2*m+1)/2 - m >= 0
m <= n, i > 0 ==> j = (m+n+1)/2 - i < (m+n+1)/2 <= (2*n+1)/2 <= n

i < m ==> j > 0;
i > 0 ==> j < n;


具体代码实现

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
if (m > n) {
m = n;
n = nums1.length;
int[] tmp = nums1;
nums1 = nums2;
nums2 = tmp;
}

int imin =0, imax=m,i,j,halfLen=(m+n+1)/2;
while (imin <= imax) {
i = (imin+imax)/2;
j = halfLen - i;
if (i < m && nums2[j-1] > nums1[i]) {
imin = i + 1;
} else if(i > 0 && nums1[i-1] > nums2[j]) {
imax = i - 1;
} else {
int left_max;
if (j==0) {
left_max = nums1[i-1];
} else if (i ==0 ) {
left_max = nums2[j-1];
} else {
left_max = Math.max(nums1[i-1],nums2[j-1]);
}
if ((m+n)%2 == 1) {
return left_max;
}
int right_min;
if (i == m) {
right_min = nums2[j];
} else if ( j == n) {

979d
right_min = nums1[i];
} else {
right_min = Math.min(nums1[i],nums2[j]);
}
return (left_max+right_min)/2.0;
}

}
return  -1;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法
相关文章推荐