- 多數元素
解法一: 遍歷整個數組,然後用另一重循環統計每個數字出現的次數。將出現次數大於len/2的數字返回。
時間複雜度:O(n^2)
class Solution {
public int majorityElement(int[] nums) {
int majorityCount = nums.length/2;
for (int num : nums) {
int count = 0;
for (int elem : nums) {
if (elem == num) {
count ++;
}
}
if (count > majorityCount) {
return num;
}
}
return -1;
}
}
解法二: 如果所有數字被單調遞增或者單調遞減的順序排了序,那麼衆數的下標爲 n/2(偶數時爲n/2+1)
時間複雜度:O(nlgn)
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
}
- 數組中的第K個最大元素
解法一: 直接使用庫函數sort排序後輸出len-k即可,時間複雜度:O(NlogN)
解法二: 快速排序的思想是每輪選中一個基準值,然後兩個指針分別從頭尾遍歷直到相遇,在該輪結束時,將基準值調換到合適的位置,使他左邊都比他小,右邊都比他大。這道題顯然可以利用這種思想,先將比基準值大的元素都分到數組的右邊,看基準值的位置是否是len-k,大了就到前半再排,小了就到後半再排,直到相等。
這種情況下時間複雜度:O(以n爲首項,1/2爲公比的等比數列的和) = O(n)
需要注意的時,這種方法對於越混亂的越高效,如果面對最壞情況(已經有序排列),這種方法會達到 O(N^2),毫無意義。所以必須隨機選擇初始基準值。
import java.util.Random;
public class Solution {
private static Random random = new Random(System.currentTimeMillis());
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
int left = 0;
int right = len - 1;
int target = len - k;
while (true) {
int index = partition(nums, left, right);
if (index == target) {
return nums[index];
} else if (index < target) {
left = index + 1;
} else {
right = index - 1;
}
}
}
public int partition(int[] nums, int left, int right) {
// 在區間隨機選擇一個元素作爲標定點
if (right > left) {
int randomIndex = left + 1 + random.nextInt(right - left);
swap(nums, left, randomIndex);
}
int pivot = nums[right];
int j = right;
for (int i = right-1; i>=left; i--) {
if (nums[i] > pivot) {
// 大於 pivot 的元素都被交換到後面
j--;
swap(nums, j, i);
}
}
swap(nums, j, right);
return j;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
- 尋找兩個有序數組的中位數
解法一: 因爲是兩個有序數組,所以只要設置兩個指針遍歷兩個數組,找到中位數即可。注意區別總長奇偶兩種情況,且兩種情況都需要遍歷len/2+1次:
如果是奇數,我們需要知道第 (len+1)/2 個數,
如果是偶數,我們需要知道第 len/2和 len/2+1 個數
時間複雜度:遍歷 len/2+1 次,len=m+n,所以時間複雜度O(m+n)O(m+n)O(m+n)。
空間複雜度:申請了常數個變量,所以空間複雜度是 O(1)O(1)O(1)。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n=nums1.length;
int m=nums2.length;
int len=n+m;
int pa=0,pb=0;//兩個指針
int before=-1,after=-1;//長爲偶數時需存儲前一個值
for(int i=0;i<len/2+1;i++){
//最後一次循環befor保存前一次,after更新
before=after;
//a,b未到頭時a值比b值小 a++ 否則 b++
//a 到頭,即b剩下的都比a大,繼續遍歷b;b到頭同理
//因爲遍歷len/2+1次,所以不會a,b同時到頭
if(pa<n & (pb>=m || nums1[pa]<nums2[pb])){
after=nums1[pa++];
}else{
after=nums2[pb++];
}
}
if(len%2==0){
return (before+after)/2.0;
}else{
return after;
}
}
}
解法2: 題目要求算法的時間複雜度爲 O(log(m + n)),所以要運用分治法,也就是上一題“輸出前K大的元素“。
時間複雜度:每進行一次循環,就減少 k/2 個元素,所以時間複雜度是 O(log(k)),而
k=(m+n)/2,所以最終的複雜度爲 O(log(m+n)。
空間複雜度:O(1)。
解法一中,我們一次遍歷就相當於去掉不可能是中位數的一個值,也就是一個一個排除。由於數列是有序的,其實我們完全可以一半兒一半兒的排除。假設我們要找第 k 小數,我們可以每次循環排除掉 k/2 個數。看下邊一個例子。
假設我們要找第 7 小的數字。
我們比較兩個數組的第 k/2 個數字,如果 k 是奇數,向下取整。也就是比較第 3 個數字,上邊數組中的 4 和下邊數組中的3,哪個小,就表明該數組的前 k/2 個數字都不是第 k 小數字,所以可以排除。也就是 1,2,3 這三個數字不可能是第7 小的數字,我們可以把它排除掉。將 1349和 45678910兩個數組作爲新的數組進行比較。
更一般的情況 A[1] ,A[2] ,A[3],A[k/2] … ,B[1],B[2],B[3],B[k/2] … ,如果
A[k/2]<B[k/2] ,那麼A[1],A[2],A[3],A[k/2]都不可能是第 k 小的數字。A 數組中比 A[k/2] 小的數有 k/2-1 個,B 數組中,B[k/2] 比 A[k/2] 小,所以比 A[k/2] 小的數字最多有 k/2-1+k/2-1=k-2個,所以 A[k/2]最多是第 k-1 小的數。而比 A[k/2] 小的數更不可能是第 k 小的數了,所以可以把它們排除。
橙色的部分表示已經去掉的數字。
由於我們已經排除掉了 3 個數字,就是這 3 個數字一定在最前邊,所以在兩個新數組中,我們只需要找第 7 - 3 = 4小的數字就可以了,也就是 k = 4。此時兩個數組,比較第 2 個數字,3 < 5,所以我們可以把小的那個數組中的 1 ,3 排除掉了。
我們又排除掉 2 個數字,所以現在找第 4 - 2 = 2 小的數字就可以了。此時比較兩個數組中的第 k / 2 = 1 個數,4 == 4,怎麼辦呢?由於兩個數相等,所以我們無論去掉哪個數組中的都行,因爲去掉 1 個總會保留 1 個的,所以沒有影響。爲了統一,我們就假設 4>4 吧,所以此時將下邊的 4 去掉。
由於又去掉 1 個數字,此時我們要找第 1 小的數字,所以只需判斷兩個數組中第一個數字哪個小就可以了,也就是 4。
所以第 7 小的數字是 4。
我們每次都是取 k/2 的數進行比較,有時候可能會遇到數組長度小於 k/2的時候。
此時 k / 2 等於 3,而上邊的數組長度是 2,我們此時將箭頭指向它的末尾就可以了。這樣的話,由於 2 < 3,所以就會導致上邊的數組 1,2 都被排除。造成下邊的情況。
由於 2 個元素被排除,所以此時 k = 5,又由於上邊的數組已經空了,我們只需要返回下邊的數組的第 5 個數字就可以了。
從上邊可以看到,無論是找第奇數個還是第偶數個數字,對我們的算法並沒有影響,而且在算法進行中,k 的值都有可能從奇數變爲偶數,最終都會變爲 1,或者由於一個數組空了,直接返回結果。
所以我們採用遞歸的思路,爲了防止數組長度小於 k/2,所以每次比較 min(k/2,len(數組)
對應的數字,把小的那個對應的數組的數字排除,將兩個新數組進入遞歸,並且 k 要減去排除的數字的個數。遞歸出口就是當 k=1
或者其中一個數字長度是 0 了。作者:windliang
鏈接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/
來源:力扣(LeetCode)
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int left = (n + m + 1) / 2;
int right = (n + m + 2) / 2;
//將偶數和奇數的情況合併,如果是奇數,會求兩次同樣的 k 。
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
}
private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
//讓 len1 的長度小於 len2,這樣就能保證如果有數組空了,一定是 len1
if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
if (len1 == 0) return nums2[start2 + k - 1];
if (k == 1) return Math.min(nums1[start1], nums2[start2]);
int i = start1 + Math.min(len1, k / 2) - 1;
int j = start2 + Math.min(len2, k / 2) - 1;
if (nums1[i] > nums2[j]) {
return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
}
else {
return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
}
}
}