[LeetCode]40.最小的k個數(TopK問題。通過維護堆、優先隊列、快排思想等解決方法)

最小的k個數

輸入整數數組 arr ,找出其中最小的 k 個數。例如,輸入4、5、1、6、2、7、3、8這8個數字,則最小的4個數字是1、2、3、4。

示例 1:

輸入:arr = [3,2,1], k = 2
輸出:[1,2] 或者 [2,1]

示例 2:

輸入:arr = [0,1,2,1], k = 1
輸出:[0]

限制:

0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000




思路

1.暴力法,先對數組進行排序(各種排序方法),再取出前k個數。

public int[] getLeastNumbers(int[] arr, int k) {
    Arrays.sort(arr);
    int[] ints = new int[k];
    for (int i = 0; i < k - 1; i++) {
        ints[i]=arr[i];
    }
    return ints;
}

2.建立大根堆解決前K小問題.(小根堆解決前K大問題)。維護一個大根堆,若堆的大小小於K,將當前值放入隊中。否則判斷當前值與堆頂元素的大小,如果當前值小於堆頂元素,再將當前值放入堆中,每放入一個值後,再要調整成爲大根堆。

    public  int[] getLeastNumbers(int[] arr,int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        int[] ints = Arrays.copyOf(arr, k);
        //構建k個數的大根堆,堆頂爲最大的數,for循環確保每個子堆都是大根堆,至少有ints.length/ 2個子堆。
        for (int i =ints.length/ 2; i >= 0; i--) {
            setHeap(ints, i, k);
        }

        //每次取數組中剩下的數與堆頂的數比較
        for (int i = k; i <arr.length; i++) {
            //如果數組中的數比堆頂的數小,則放入堆頂,再構建一次大根堆
            if (ints[0]>arr[i]){
                ints[0]=arr[i];
                setHeap(ints,0,k);
            }
        }
        return ints;
    }

    public  void  setHeap(int [] array,int parent,int length){
        int temp=array[parent];
        int child=parent*2+1;
        //循環判斷父節點的值是否小於子節點,是則替換
        while (length>child){
            //取出子節點中較大的數的索引
            if(child+1<length && array[child]<array[child+1]){
                child++;
            }
            //如果父節點值大於子節點則不用交換值
            if(temp>=array[child]){
                break;
            }
            //交換父節點和子節點的值
            array[parent]=array[child];
            parent=child;
            child=2*child+1;
        }
        array[parent]=temp;
    }

3.使用優先隊列代替堆,優先隊列的實現原理與大根堆/小根堆相同。效率不如手寫堆。(也許是調用庫函數會加載其他東西)

public int[] getLeastNumbers(int[] arr, int k) {
        if ( k==0 || arr.length==0){
            return new int[0];
        }
        PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> o2-o1);
        //優先隊列默認爲升序排列,重寫compare方法爲降序排列。使用lombda表示,等價於以下代碼
//        PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
//            @Override
//            public int compare(Integer o1, Integer o2) {
//                return o2-o1;
//            }
//        });

        for (int i : arr) {
            if (queue.size()<k){
                queue.offer(i);
            }else if (queue.peek()>i){ //當優先隊列滿k個後,取最大值和待放入的值比較,如果待放入值小,則放入。
                queue.poll();
                queue.offer(i);
            }
        }

        int[] ints = new int[queue.size()];
        for (int i = 0; i < ints.length; i++) {
            ints[i]=queue.poll();
        }
        
        return ints;
    }

4.快速排序思想,但是不對數組完全排序,只需要有選擇性的分段排序,當確定基準等於K時,則K左邊的數都比k小。右邊的數不需要處理。

    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        //k-1爲我們要找的基準的下標。
        return quickSort(arr, 0, arr.length - 1, k - 1);
    }

    public int[] quickSort(int[] arr, int left, int right, int k) {
        // 對數組進行分割,取出下次分割的基準標號
        int division = division(arr, left, right);
        //如果基準與k正好相等,則返回k左邊的部分。
        if (division == k) {
            return Arrays.copyOf(arr, k + 1);
        }
        // 如果k在基準的右邊,則對右段進行遞歸排序。
        // 如果k在基準的坐邊,則對左段進行遞歸排序。
        return division < k ? quickSort(arr, division + 1, right, k) : quickSort(arr, left, division - 1, k);

    }

    public int division(int[] list, int left, int right) {
        // 以最左邊的數(left)爲基準
        int base = list[left];
        while (left < right) {
            // 從序列右端開始,向左遍歷,直到找到小於base的數
            while (left < right && list[right] >= base) {
                right--;
            }
            // 找到了比base小的元素,將這個元素放到最左邊的位置
            list[left] = list[right];

            // 從序列左端開始,向右遍歷,直到找到大於base的數
            while (left < right && list[left] <= base) {
                left++;
            }
            // 找到了比base大的元素,將這個元素放到最右邊的位置
            list[right] = list[left];
        }

        // 最後將base放到left位置。都比此時,left位置的左側數值應該left小;
        // 而left位置的右側數值應該都比left大。
        list[left] = base;
        return left;
    }

5.題目中規定數字不大於一萬,可以使用頻次數字處理,然後遍歷頻次數組,獲取前K個數。

public int[] getLeastNumbers(int[] arr, int k) {
    if (k == 0 || arr.length == 0) {
        return new int[0];
    }
    // 統計每個數字出現的次數
    int[] hash = new int[10001];
    for (int num : arr) {
        hash[num]++;
    }
    
    int[] ans = new int[k];
    int count=0;
    for (int num = 0; num < hash.length; num++) {
        if (count == k) {
            break;
        }
        //從頻次數組中取出前k個數。
        while (hash[num]>0 && k>count){
            ans[count++]=num;
            hash[num]--;
        }
    }
    
    return ans;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章