找出陣列中第k大的數(時間複雜度分析、C++程式碼實現). TopK in array. ( leetcode
阿新 • • 發佈:2019-01-01
找出陣列中第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