劍指Offer-31-最小的K個數

題目

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

解析

思路一

顯然最簡答作法就是對原數組排序,取前k個就行。java

Note: 這裏能夠分狀況的:面試

  1. 若是k遠小於n, 能夠利用一次冒泡或者選擇算法,選擇出當前序列中最小的值,複雜度爲O(nk)
  2. 若是k沒有遠小於n, 那麼選擇O(nlogn)算法最佳
/** * 排序的作法 * @param input * @param k * @return */
    public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<>();
        if(input == null || k <= 0 || k > input.length) {
            return result;
        }
        Arrays.sort(input);
        for(int i = 0; i < k; i++) {
            result.add(input[i]);
        }
        return result;
    }
複製代碼

思路二

咱們注意到題目並無要求輸出的最小k個數必須是有序的,因此咱們能夠利用快排中partion函數的思想來作作題。 由於partion可使得序列分爲2部分:左邊的值都小於哨兵,右邊的值都大於哨兵。因此咱們只要找處處於第k位置的哨兵便可,也就是說找到第k大的值所在的位置便可,那麼它的左邊的k-1值都小於等於第k大值。顯然,前k個值即爲咱們所求的最小k個數。在咱們的劃分過程有3種狀況:算法

  1. 哨兵的位置大於k,說明第k大的數在左邊,繼續遞歸處理左部分便可。
  2. 哨兵的位置小於k,說明第K大的數在右邊,繼續遞歸處理有部分便可。
  3. 哨兵的位置等於k,說明該哨兵即爲第K大的值,其左邊k-1個數都小於等於它,所以輸出前k個即爲所求的結果。
/** * 基於快排的劃分函數的思想來作的。 * @param input * @param k * @return */
    public static ArrayList<Integer> GetLeastNumbers_Solution2(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<>();
        if(input == null || k <= 0 || k > input.length) {
            return result;
        }
        findKthValue(input, 0, input.length - 1, k - 1);
        for(int i = 0; i < k; i++) {
            result.add(input[i]);
        }
        return result;
    }

    public static void findKthValue(int[] input, int low, int high, int k) {
        if(low < high) {
            int pivot = new Random().nextInt(high - low + 1) + low;
            swap(input, pivot, high);
            int index = low;
            for(int i = low; i < high; i++) {
                if(input[i] < input[high]) {
                    swap(input, i, index);
                    index++;
                }
            }
            swap(input, index, high);
            if(index > k) {
                findKthValue(input, low, index - 1, k);
            }else if(index < k) {
                findKthValue(input, index + 1, high, k);
            }
        }
    }
複製代碼

思路3

這是典型的Top-K問題,即從n個數中找出最小的k個數或者最大的k個數問題。
咱們一般的作法用一個容量爲k的容器來存放這k個最小的值。咱們只需遍歷一遍原數組,就能獲得最小的k個數。數組

  1. 起初容器是空,當已遍歷的數的個數小於容器的容量k時,直接向容器中添加該值。
  2. 當容器的容量已滿,則判斷該容器中最大值是否大於待插入的點:
    1. 若大於,則從容器中刪除該最大值,添加待插入的點
    2. 若小於或者等於,則不作任何操做,繼續遍歷下一個值

問題轉化爲如何高效率獲得容器中的最大值。一個優雅的數據結構完美的解決此題,即堆結構,分爲大根堆或者小根堆。顯然這裏應該選擇大根堆。在大根堆中,根節點大於左子樹和右子樹中全部點,因此咱們只需訪問根節點便可獲得k容量的最大值,且數據結構能夠對插入的值進行動態調整堆結構,使得知足大根堆。關於堆的具體代碼,之後我單獨寫一個博客,這裏再也不累述了。
在Java中,沒有專門的堆數據結果,不過有基於堆結構的優先隊列,因此這裏採用優先隊列並自定義比較器,來知足大根堆的需求。數據結構

/** * Topk問題 * @param input * @param k * @return */
    public static ArrayList<Integer> GetLeastNumbers_Solution3(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<>();
        if(input == null || k <= 0 || k > input.length) {
            return result;
        }
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(k, new Comparator<Integer>() {

            //由於要知足大根堆需求,因此使用自定義比較器,比較策略爲o1大於o2時,o1放o2的前面
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        for(int i = 0; i < input.length; i++) {
            if(i < k) {
                priorityQueue.add(input[i]);
            } else if(input[i] < priorityQueue.peek()) {
                priorityQueue.poll();
                priorityQueue.add(input[i]);
            }
        }
        result.addAll(priorityQueue);
        return result;
    }
複製代碼

總結

多結合排序算法和常見的數據結構來簡化題目。dom

文章收錄在[我的專欄(upadating)](https://blog.csdn.net/column/details/23876.html),期待與你一塊兒KO常見面試題。
相關文章
相關標籤/搜索