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

来源:互联网 发布:mac奶瓶双用粉底液色号 编辑:程序博客网 时间:2024/06/09 15:29

[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_AA[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_BB[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]

合并AB左右子集

left_Aleft_B放在同一集合,把right_Aright_B放在另外一个集合,命名为left_partright_part:

      left_part          |        right_partA[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, 我们只需要确定ij的关系: 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=mn<m,那么j<0,这会得到错误的答案。

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

算法过程

  1. 设置imin = 0, imax = 0, 然后在[imin, imax]这个范围里面找
  2. 设置i和j:i = (imin + imax)/2, j = (m + n + 1)/2 - i
  3. 这样我们就保证了左边部分和右边部分已经长度相同。下面我们有三种情况需要考虑:
    1. 如果i和j满足B[j-1] <= A[i] and A[i-1] <= B[j],意味当前ij已经就是我们要找的位置,停止查找。
    2. 如果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,然后回到第二步。
    3. 如果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=0A[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 <= ni < 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) {                right_min = nums1[i];            } else {                right_min = Math.min(nums1[i],nums2[j]);            }            return (left_max+right_min)/2.0;        }    }    return  -1;}
原创粉丝点击