LeetCode-Median of Two Sorted Arrays

来源:互联网 发布:各国语言在线翻译软件 编辑:程序博客网 时间:2024/05/29 15:00

算法分析与设计,第一周博客

Median of Two Sorted Arrays

Description:

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
本题的大致意思就是说,有两个已经排好序的数组,要求找到两个数组的中位数,并且要求算法的时间复杂度为 O(log (m+n))。

首先不考虑考虑算法的复杂度,那么最简单的方法就是把这两个有序的数组合并成一个有序数组,然后在去找这个合并后的数组的中位数。

 public double findMedianSortedArrays(int[] nums1, int[] nums2) {        int n = nums1.length, m = nums2.length;        int sum = n+m;        int[] merge = new int[sum];        int i = 0, j = 0, k = 0;        while (i < n && j < m) {            if (nums1[i] < nums2[j]) {                merge[k] = nums1[i];                ++i;            } else {                merge[k] = nums2[j];                ++j;            }            ++k;        }        while (i < n) {            merge[k++] = nums1[i++];        }        while (j < m) {            merge[k++] = nums2[j++];        }        if (sum%2 == 0) {        double one = merge[sum/2-1];        double two = merge[sum/2];        return (one+two)/2;        } else {        return (double)merge[sum/2];        }    }
可以看出来,因为需要把两个数组合并,所以它的时间复杂度为O(n+m),并不符合题目的要求。

现在,考虑O(log(m+n))的算法。在常用的算法里面,能够达到O(log(n))数量级的算法最常见的就是二分法了:每次把大问题分成两部分,通过判断,把其中的一部分删去,留下另一部分进行递归求解,直到问题规模小到可以直接解决。

那么,对于这一题,应该如何应用二分法呢。我的解题思路是:因为有两个数组,需要找到的是中位数,也就是处于数组中间的那个数。设中位数在整个数组中的位置是第k位,那么就去分别找这两个数组的第k/2位的数字(设为a和b,分别对应数组A和数组B),并比较它们的大小,情况无非有以下两种:

  1. a > b:那么表明第k位的数在A的前半部分和B的后半部分之间。
  2. a <= b:那么表明第k位的数在A的后半部分和B的前半部分之间。

有了这些结论,就可以递归调用不断的减小数组的范围。

                int half = k/2;int mid1 = i+half >= n ? n-1 : i+half;int mid2 = j+(half) >= m ? m-1 : j+(half);int key1 = nums1[mid1];int key2 = nums2[mid2];if (key1 < key2) {// 数组2指向第1个元素,并且数组1指向的元素恰好是第k个元素,则返回数组1所指的元素,下同if (mid2 == j && mid1-i == k) {return key1;} // 如果key1是数组1的第一个元素,那么key1也排除,下同if (mid1 <= i)++mid1;// 把搜索范围缩小到数组1的[mid1,n),数组2的[j,m),并且搜索元素变为第(k-(mid1-i))位,下同return search(nums1, mid1, n, nums2, j, m, k-(mid1-i));} else {if (mid1 == i && mid2-j == k) {return key2;}if (mid2 <= j)++mid2;return search(nums1, i, n, nums2, mid2, m, k-(mid2-j));} 

接下来考虑这个算法的时间复杂性。两个数组的和为n+m,需要寻找的数为第k位。第i次递归可以减少k/(2^i))的搜索范围:

  1. 第零次的搜索范围是[0,n)和[0,m),第k位。
  2. 第一次的搜索范围是[k/2,n)和[0,k/2),第k/2位。
  3. ...
  4. 第i次的搜索范围是[k/2+k/4+...+k/(2^i),n), [0, k/(2^i)),第k/(2^i)位。
那么搜索将在i=log(k)时停止,而每次搜索的花销是若干次取值与比较,时间复杂度是O(1),那么这个算法的总体复杂度是:O(log(k)) = O(log(n+m)),符合题目的要求。

整个程序如下:

public class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {        int n = nums1.length, m = nums2.length;        int sum = n+m;        if (sum%2 == 0) {            // 总个数为偶数时,需要取中间两个元素的平均数        double one = search(nums1, 0, n, nums2, 0, m, sum/2-1);        double two = search(nums1, 0, n, nums2, 0, m, sum/2);        return (one+two)/2;        } else {        return search(nums1, 0, n, nums2, 0, m, sum/2);        }    }    // 搜索数组1的[i,n),数组2的[j,m),搜索元素为第k位private double search(int[] nums1, int i, int n, int[] nums2, int j, int m, int k) {// 如果数组nums1已经检查完毕,那么就直接去数组nums2里面直接找,下同if (i >= n && j+k < m) {return nums2[j+k];}if (j >= m && i+k < n) {return nums1[i+k];}        // 已经只剩一个位置了,只需要返回两个数组中更小的那个if (k <= 0) {return nums1[i] < nums2[j] ? nums1[i] : nums2[j];}int half = k/2;int mid1 = i+half >= n ? n-1 : i+half;int mid2 = j+(half) >= m ? m-1 : j+(half);int key1 = nums1[mid1];int key2 = nums2[mid2];if (key1 < key2) {// 数组2指向第1个元素,并且数组1指向的元素恰好是第k个元素,则返回数组1所指的元素,下同if (mid2 == j && mid1-i == k) {return key1;} // 如果key1是数组1的第一个元素,那么key1也排除,下同if (mid1 <= i)++mid1;// 把搜索范围缩小到数组1的[mid1,n),数组2的[j,m),并且搜索元素变为第(k-(mid1-i))位,下同return search(nums1, mid1, n, nums2, j, m, k-(mid1-i));} else {if (mid1 == i && mid2-j == k) {return key2;}if (mid2 <= j)++mid2;return search(nums1, i, n, nums2, mid2, m, k-(mid2-j));} }    }