1. 程式人生 > >找出陣列中第k大的數(時間複雜度分析、C++程式碼實現). TopK in array. ( leetcode

找出陣列中第k大的數(時間複雜度分析、C++程式碼實現). TopK in array. ( leetcode

找出陣列中第k大的數. TopK in array. ( leetcode - 215 )

最近面試過程中遇到的一個題目,也是大資料時代常見的題目,就來總結一下。

面試題目

1、10億數中,找出最大的100個數。用你能想到的最優的時間和空間效率。
2、寫出來之後,問時間空間複雜度是多少?如何計算?

LeetCode 215:

Find the kth largest element in an unsorted array. 
Note that it is the kth largest element in the sorted order, not the kth distinct element.

For example,
Given [3,2,1,5,6,4] and k = 2, return 5.

Note: 
You may assume k is always valid, 1 ≤ k ≤ array's length.

解題思路:

既然要求了時間空間複雜度,就先不考慮對所有數就行排序的方法了(雖然可行,但是效率肯定達不到面試官要求)。

思路:  
1.維護一個k大小的有序序列,然後遍歷剩餘資料,依次和有序序列中的數比較;
2.如果比有序序列中的最小值大,則將最小值換出;
3.重新對序列排序,直至遍歷完所有資料。

Solution 1: 使用最大堆

Using Max-Heap.
時間、空間複雜度分析。
1) 建堆 Build Heap.
    Time O(K) about, SpaceO(K)
        高度 Height: 1-h, h = log(n+1);
        總節點數 Total-Node-Number: 1-n, n = 2^n - 1;
        層數 Layer-Number: 1-i
        每層節點數 Node-Number-per-Layer: 2^(i-1)
        最壞情況,
            倒數第一層節點需要向下比較 0 次,
            倒數第二層節點需要向下比較 1 次,
            倒數第三層節點需要向下比較 2 次,
            ...
            (每次只需要比較 與根節點交換的 分支即可)
        Time(h) = 2^(h) * 0 + 2^(h-1) * 1 + 2^(h-2) * 2 + ... + 2^1 * (h-1)
        錯位相減法:
        等式兩邊同乘以 2,得:
        2*Time(h) = 2^(h+1) * 0 + 2^(h) * 1 + 2^(h-1) * 2 + ... + 2^2 * (h-1)
        Time(h) = 2*Time(h) - Time(h)
                = 2^(h) * 1 + 2^(h-1) + 2^(h-2) + ... + 2^2 - 2^1(h-1)
                = { 2^h + 2^(h-1) + 2^(h-2) + ... + 2^2 } - 2(h-1)
                = { (4 - 2^(h+1) ) / (1-2) } - 2h +2         // 大括號{}內是等比數列求和公式.
                = {2^(h+1) - 4} - 2h + 2
                = 2^(h+1) - 2h - 2
        lim{Time(h)} = lim{n - 2log(n) - 2}  = n             // h = log(n+1), n 足夠大時,求極限.
    所以,建堆的時間複雜度大約為 Time O(n).

2) 調整單個節點 Heapify.
    Time O(logN)
        每次調整隻需選擇當前節點的一個分支,因此調整節點的時間複雜度 O(logN).

3)調整堆
    Time O(nlogn)
即: 堆排序heap_sort時,交換堆頂元素和堆尾元素後,重新調整堆,對n/2元素都進行一次調整,因此 Time O(nlogn).
class SolutionHeap{

public:

    int findKthLargest(vector<int> &nums, int k)
    {
        int size = nums.size();
        int index = 0;
        vector<int> k_size_array;
        k = k < size ? k : size;

        // 前 k 個元素入堆.
        for (index = 0; index < k; index ++)
        {
            k_size_array.push_back(nums[index]);
        }

        // 建 size = k 大小的小根堆.
buildMinHeapify(k_size_array); // 其餘元素依次和堆頂元素(最大值中的最小值)比較 // 如果大於堆頂元素,則交換,重新調整堆(從根節點調整依次即可)。 for (; index < size; index++) { if (k_size_array[0] < nums[index]) { swap(k_size_array[0], nums[index]); minHeapify(k_size_array, 0); //print_array(k_size_array); } } // 堆頂元素即第k大元素,return. return k_size_array[0]; } void print_array(vector<int> &nums) { vector<int>::iterator iter; for (iter = nums.begin(); iter != nums.end(); iter++) { cout << *iter << endl; } cout << endl; } private: inline int leftChild(int index) { return ((index << 1) + 1); } inline int rightChild(int index) { return ((index << 1) + 2); } inline int parent(int index) { return ((index - 1) >> 1); } // 調整堆 void minHeapify(vector<int> &array, int index) { int length = array.size(); int left = leftChild(index); int right = rightChild(index); int least = index; if (left < length && array[index] > array[left]) // 切記先判斷下標是否越界 { least = left; } if (right < length && array[least] > array[right]) // 切記先判斷下標是否越界 { least = right; } if (least != index) { swap(array[least], array[index]); minHeapify(array, least); } } // 建小根堆 void buildMinHeapify(vector<int> &array) { int index = parent(array.size() - 1); for (; index >= 0; index--) { minHeapify(array, index); } } };

Solution 2:優先順序佇列

具體程式碼見:https://github.com/Jackson-Y/Machine-Learning/blob/master/algorithms/top_k_in_array.cpp

Solution 3:multiset

具體程式碼見:https://github.com/Jackson-Y/Machine-Learning/blob/master/algorithms/top_k_in_array.cpp

Solution 4:Partition(idea from quick-sort)

具體程式碼見:https://github.com/Jackson-Y/Machine-Learning/blob/master/algorithms/top_k_in_array.cpp