力扣練習2:分治

  1. 多數元素
    解法一: 遍歷整個數組,然後用另一重循環統計每個數字出現的次數。將出現次數大於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];
    }
}

  1. 數組中的第K個最大元素
    解法一: 直接使用庫函數sort排序後輸出len-k即可,時間複雜度:O(Nlog⁡N)
    解法二: 快速排序的思想是每輪選中一個基準值,然後兩個指針分別從頭尾遍歷直到相遇,在該輪結束時,將基準值調換到合適的位置,使他左邊都比他小,右邊都比他大。這道題顯然可以利用這種思想,先將比基準值大的元素都分到數組的右邊,看基準值的位置是否是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;
    }
}


  1. 尋找兩個有序數組的中位數
    解法一: 因爲是兩個有序數組,所以只要設置兩個指針遍歷兩個數組,找到中位數即可。注意區別總長奇偶兩種情況,且兩種情況都需要遍歷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 小的數了,所以可以把它們排除。

橙色的部分表示已經去掉的數字。

image.png

由於我們已經排除掉了 3 個數字,就是這 3 個數字一定在最前邊,所以在兩個新數組中,我們只需要找第 7 - 3 = 4小的數字就可以了,也就是 k = 4。此時兩個數組,比較第 2 個數字,3 < 5,所以我們可以把小的那個數組中的 1 ,3 排除掉了。

image.png

我們又排除掉 2 個數字,所以現在找第 4 - 2 = 2 小的數字就可以了。此時比較兩個數組中的第 k / 2 = 1 個數,4 == 4,怎麼辦呢?由於兩個數相等,所以我們無論去掉哪個數組中的都行,因爲去掉 1 個總會保留 1 個的,所以沒有影響。爲了統一,我們就假設 4>4 吧,所以此時將下邊的 4 去掉。

image.png

由於又去掉 1 個數字,此時我們要找第 1 小的數字,所以只需判斷兩個數組中第一個數字哪個小就可以了,也就是 4。

所以第 7 小的數字是 4。

我們每次都是取 k/2 的數進行比較,有時候可能會遇到數組長度小於 k/2的時候。

image.png

此時 k / 2 等於 3,而上邊的數組長度是 2,我們此時將箭頭指向它的末尾就可以了。這樣的話,由於 2 < 3,所以就會導致上邊的數組 1,2 都被排除。造成下邊的情況。

image.png

由於 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));
        }
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章