LeetCode演算法4:java 尋找兩個有序陣列的中位數
題目描述
給定兩個大小為 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
問題評價
該題技巧性較強,需要從設計上實現二分法來達到目的複雜度;
邊界處理仍然是問題的難點;
解題思路
尋找中位數,最終定位為尋找第K位的值。k是根據總體資料量的奇、偶性計算得到的。
1 、假設nums1.length = m, nums2.length = n; m < n。
2、若(m + n) % 2 == 0
, 表示兩陣列之和為偶數,應該是有兩個中位數,需要求均值。如果和為奇數,則只有一位,在程式碼設計最初考慮該問題。
3、為了使得方法的統一,在最初時,對陣列進行處理,統一使得傳進方法的短陣列為nums1,這樣在後續進行k/2與。
4、如果len1-start1 == 0
,則表示nums1已經全部加入前k個了,則第k個為nums2[k -1]; 在方法findKth()中的k是一直變化的,初始時,k為兩個陣列中排序之後的第k個數的位置;k在方法中的真正含義為“還需要找到多少個數才能達到k個”;因此假設nums1.length ==0;
len1-start1 == 0
, 則中位數就是nums2[k - 1],即在nums1中找到了0個數,還需要找k個數,第k個數就是nums[k - 1];
5、 如果k == 1,則表示前k-1小的數已經找過了,則第k個數肯定是nums1[start1]
和nums2[start2]
中較小的那個數。
6、 下面接著就是常規的情況:即nums1中包含一部分k,nums2中也包含一部分的k,因此就從每個陣列的k/2那裡開始比較(也相當於每次都會有一半的數被加入前k個,因此時間複雜度為O(log(m + n)))
。
採用p1和p2分別記錄當前nums1和nums2需要比較的那個位,由於nums1比較短,因此有可能k/2的位置已經超出了nums1的長度,因此nums1還需要做特殊處理,即求解p1處所示;由於p1做了特殊處理,那p2也就要做特殊處理。總之,start1~p1
start2~p2
的和一定為k。
1)若nums1[p1 - 1] < nums[p2 - 1]
,則表明【start1, p1)之間的值在前k個數中;
2)若nums[p1 - 1] > nums2[p2- 1]
,則表明【start2, p2)之間的值在前k個數中;
3)若兩值相等,則表明【start1, p1)+【start2, p2)的個數為k,則結果直接返回其中一個即可。
邊界問題
1、為什麼比較的p1和p2的前一個位的數,而不是p1和p2位置的數呢?這就是邊界問題。
舉例說明:假設start1== start2 == 0
, 則p1 = Math.min(len1, k / 2)
; p2 = k - p1
,即p1 + p2 == k
;;假設p1 = 5, p2 = 7; 則k = 12; 在陣列中nums[5]
其實是第6個數,nums[7]
其實是第8個數,所以我們比較的是nums1[p1 - 1]
與nums2[p2 - 1]
的值。
2、注意每個陣列的start位置,其實是有效資料的前一位,因此可以直接做差來求資料的個數。例如[1,3,4,5],記錄為start=0,length=4。在後續進行推進修改時,start仍舊為有效資料位的前一位。
程式碼
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int size = len1 + len2;
if(size % 2 == 1)
return findKth(nums1, 0, len1, nums2, 0, len2, size / 2 + 1);
else
return (findKth(nums1, 0, len1, nums2, 0, len2, size / 2)
+ findKth(nums1, 0, len1, nums2, 0, len2, size / 2 + 1)) /2;
}
public double findKth(int[] nums1, int start1, int len1, int[] nums2, int start2, int len2, int k)
{
if(len1 - start1 > len2 -start2) // 傳進來的時候統一讓短的陣列為nums1
return findKth(nums2, start2, len2, nums1, start1, len1, k);
if(len1 - start1 == 0) // 表示nums1已經全部加入前K個了,第k個為nums2[k - 1];
return nums2[k - 1];
if(k == 1)
return Math.min(nums1[start1], nums2[start2]);
// k==1表示已經找到第k-1小的數,下一個數為兩個陣列start開始的最小值
int p1 = start1 + Math.min(len1 - start1, k / 2); // p1和p2記錄當前需要比較的那個位
int p2 = start2 + k - p1 + start1;
if(nums1[p1 - 1] < nums2[p2 - 1])
return findKth(nums1, p1, len1, nums2, start2, len2, k - p1 + start1);
else if(nums1[p1 - 1] > nums2[p2 -1])
return findKth(nums1, start1, len1, nums2, p2, len2, k - p2 + start2);
else
return nums1[p1 - 1];
}
}