Leetcode 劍指 Offer 40. 最小的k個數

有夢想的coder發表於2020-11-24

輸入整數陣列 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]

方法一:最直接的思路,簡單題就要對應簡單做法。用sort函式對 arr 排序,再取其前 k 個數即可。先放C++程式碼,思路簡單易懂。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(vector<int> inputvector){
    for(int i:inputvector){
        cout<<i<<endl;
    }
}
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if(arr.empty() || k == 0) {return {};}
        vector<int> res;
        sort(arr.begin(), arr.end());
        for(int i : arr) {
            if(k <= 0) {break;}
            res.push_back(i);
            -- k;
        }
        return res;
    }
};

int main()
{   
    vector<int> myvector{4,5,1,6,2,7,3,8};
    Solution S;
    print(S.getLeastNumbers(myvector,4));
    system("pause");
    return 0;
}

在這裡插入圖片描述

方法二:利用最大堆(priority_queue)。

  1. 先將 arr 的前 k 個數入堆,因為是最大堆,所以堆頂的數一定是最大的。
  2. 那麼我們再從 arr 的第 k + 1 個元素迭代起,凡是遇到比當前堆頂元素小的,就將該元素 push 入堆並 pop 掉堆頂元素。
  3. 注意,每次執行完第二步之後,堆會自動更新,以保證堆頂元素始終為堆中所有元素的最大值。
    先放C++程式碼,思路清晰明瞭。
#include <iostream>
#include <vector>
#include<queue>
using namespace std;
void print(vector<int> inputvector){
    for(int i:inputvector){
        cout<<i<<endl;
    }
}
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if(arr.empty() || k == 0) {return {};}
        vector<int> res(k);
        priority_queue<int> max_heap;

        for(int i = 0; i < k; ++i) {max_heap.push(arr[i]);} // 用 arr 的前 k 個數填充最大堆
        for(int i = k; i < arr.size(); ++i) {
            if(arr[i] < max_heap.top()){
                max_heap.pop();
                max_heap.push(arr[i]); // 迴圈更新最大堆
            }
        }
        for(int i = 0; i < k; ++i) {
            res[i] = max_heap.top(); // 填充 res
            max_heap.pop();
        }

        return res;
    }
};

int main()
{   
    vector<int> myvector{4,5,1,6,2,7,3,8};
    Solution S;
    print(S.getLeastNumbers(myvector,4));
    system("pause");
    return 0;
}

在這裡插入圖片描述

方法三:快速選擇——退化的快速排序。先放C++程式碼,思路清晰明瞭,註釋都已寫好在程式碼中。

#include <iostream>
#include <vector>
#include<queue>
using namespace std;
void print(vector<int> inputvector){
    for(int i:inputvector){
        cout<<i<<endl;
    }
}
class Solution {
private:
    vector<int> res;
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if(arr.empty() || k == 0) {return {};}
        return quickSelection(arr, 0, arr.size() - 1, k - 1); // 注意第 k 個數對應的下標是 k - 1
    }
    vector<int> quickSelection(vector<int>& arr, int left, int right, int index) {
        // partition函式將一個區間內所有小於下標為 j 的數放在 j 左邊,大於下標為 j 的數放在 j 右邊
        int j = partition(arr, left, right); 
        
        if(j == index) { // 若 j 剛好等於 k - 1,將 arr[0] 至 arr[j] 輸入 res
            for(int i = 0; i < j + 1; ++i) {res.push_back(arr[i]);}
            return res;
        }
        // 若 j 小於 k - 1,將區間變成 [j + 1, right];反之,區間變成 [left, j - 1]
        return j < index ? quickSelection(arr, j + 1, right, index) : quickSelection(arr, left, j - 1, index);
    }
    int partition(vector<int>& arr, int left, int right) {
        int value = arr[left];
        int i = left, j = right + 1;

        while(true) {
            while(++ i <= right && arr[i] < value); // 找到從左往右第一個大於等於 value 的下標
            while(-- j >= left && arr[j] > value); // 找到從右往左第一個小於等於 value 的下標
            if(i >= j) {break;} // 如果找不到,說明已經排好序了,break
            swap(arr[i], arr[j]); // 如果找到了,交換二者
        }
        swap(arr[left], arr[j]); // arr[j]是小於 value 的,這一步使得所有小於下標為 j 的數都在 j 左邊

        return j;
    }
    void swap(int& a, int& b) { 
        int temp = a;
        a = b;
        b = temp;
    }
};

int main()
{   
    vector<int> myvector{4,5,1,6,2,7,3,8};
    Solution S;
    print(S.getLeastNumbers(myvector,4));
    system("pause");
    return 0;
}

在這裡插入圖片描述

相關文章