1.題目描述
給定兩個大小為 m 和 n 的有序數組 nums1 和 nums2。
請你找出這兩個有序數組的中位數,并且要求算法的時間復雜度為 O(log(m + n))。
假設 nums1 和 nums2 不會同時為空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
則中位數是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
則中位數是 (2 + 3)/2 = 2.5
2.思路分析
1.首先求中位數:如果是奇數:k/2+1,如果是偶數:(k/2+k/2+1)/2。數組從零開始所以要往前移一位,且要以float類型輸出。
2.最先想到的方法:歸并排序?排成一個有序的數組,從而求得。
時間復雜度:遍歷全部數組 O(m+n)
空間復雜度:開辟了一個數組,保存合并后的兩個數組 O(m+n)
3.時間復雜度都達不到題目的要求 O(log(m+n)。看到 log,很明顯,我們只有用到二分的方法才能達到。我們不妨用另一種思路,題目是求中位數,其實就是求第(m+n)/2小數的一種特殊情況。
=====>求兩個有序數組的第K個數
如圖兩個數組,假設我們要找第 7 小的數字。
示例
我們比較兩個數組的第 k/2 個數字,如果 k 是奇數,向下取整。也就是比較第 3個數字,上邊數組中的 4和下邊數組中的3,如果哪個小,就表明該數組的前 k/2 個數字都不是第 k 小數字,所以可以排除。也就是 1,2,3 這三個數字不可能是第 7 小的數字,我們可以把它排除掉。將 1349和 45678910 兩個數組作為新的數組進行比較。
依次遞歸~
出口的條件是:
①有一個數組為空
②所求得第k個值為1,則返回兩個數組最小的元素
3.代碼實現
暴力:歸并排序
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int[] nums;
int m = nums1.length;
int n = nums2.length;
nums = new int[m + n];
if (m == 0) {
if (n % 2 == 0) {
return (nums2[n / 2 - 1] + nums2[n / 2]) / 2.0;
} else {
return nums2[n / 2];
}
}
if (n == 0) {
if (m % 2 == 0) {
return (nums1[m / 2 - 1] + nums1[m / 2]) / 2.0;
} else {
return nums1[m / 2];
}
}
int count = 0;
int i = 0, j = 0;
while (count != (m + n)) {
if (i == m) {
while (j != n) {
nums[count++] = nums2[j++];
}
break;
}
if (j == n) {
while (i != m) {
nums[count++] = nums1[i++];
}
break;
}
if (nums1[i] < nums2[j]) {
nums[count++] = nums1[i++];
} else {
nums[count++] = nums2[j++];
}
}
if (count % 2 == 0) {
return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;
} else {
return nums[count / 2];
}
}
二分法
public float findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
// 處理任何一個nums為空數組的情況
if (m == 0) {
if (n % 2 != 0)
return 1.0 * nums2[n / 2];
return (nums2[n / 2] + nums2[n / 2 - 1]) / 2.0;
}
if (n == 0) {
if (m % 2 != 0)
return 1.0 * nums1[m / 2];
return (nums1[m / 2] + nums1[m / 2 - 1]) / 2.0;
}
int total = m + n;
// 總數為奇數,找第 total / 2 + 1 個數
if ((total & 1) == 1) {
return find_kth(nums1, 0, nums2, 0, total / 2 + 1);
}
// 總數為偶數,找第 total / 2 個數和第total / 2 + 1個數的平均值
return (find_kth(nums1, 0, nums2, 0, total / 2) + find_kth(nums1, 0, nums2, 0, total / 2 + 1)) / 2.0;
}
// 尋找a 和 b 數組中,第k個數字
float find_kth(int[] a, int a_begin, int[] b, int b_begin, int k) {
// 當a 或 b 超過數組長度,則第k個數為另外一個數組第k個數
if (a_begin >= a.length)
return b[b_begin + k - 1];
if (b_begin >= b.length)
return a[a_begin + k - 1];
// k為1時,兩數組最小的那個為第一個數
if (k == 1)
return Math.min(a[a_begin], b[b_begin]);
int mid_a = Integer.MAX_VALUE;
int mid_b = Integer.MAX_VALUE;
// mid_a / mid_b 分別表示 a數組、b數組中第 k / 2 個數
if (a_begin + k / 2 - 1 < a.length)
mid_a = a[a_begin + k / 2 - 1];
if (b_begin + k / 2 - 1 < b.length)
mid_b = b[b_begin + k / 2 - 1];
// 如果a數組的第 k / 2 個數小于b數組的第 k / 2 個數,表示總的第 k 個數位于 a的第k / 2個數的后半段,或者是b的第 k
// / 2個數的前半段
// 由于范圍縮小了 k / 2 個數,此時總的第 k 個數實際上等于新的范圍內的第 k - k / 2個數,依次遞歸
if (mid_a < mid_b)
//這里第二次遞歸時,k=k-k/2;不能是k=k/2,因為我們只是排除前面的k/2個
return find_kth(a, a_begin + k / 2, b, b_begin, k - k / 2);
// 否則相反
return find_kth(a, a_begin, b, b_begin + k / 2, k - k / 2);
}