算法分析与设计,第一周博客
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),并比较它们的大小,情况无非有以下两种:
- a > b:那么表明第k位的数在A的前半部分和B的后半部分之间。
- 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))的搜索范围:
- 第零次的搜索范围是[0,n)和[0,m),第k位。
- 第一次的搜索范围是[k/2,n)和[0,k/2),第k/2位。
- ...
- 第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));
}
}
}