1. 程式人生 > >【LeetCode & 劍指offer刷題】查詢與排序題2:40 最小的k個數(對應Kth Largest Element in an Array)

【LeetCode & 劍指offer刷題】查詢與排序題2:40 最小的k個數(對應Kth Largest Element in an Array)

【LeetCode & 劍指offer 刷題筆記】目錄(持續更新中...)

40 最小的k個數

題目描述

輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,   /* //暴力法:sort, O(nlogn) //方法一:使用自帶的stl函式 #include <algorithm> using namespace std;
class Solution { public:     vector<int> GetLeastNumbers_Solution(vector<int> input, int k)     {         if(k<=0 || k > input.size()) return vector<int>(); //處理非法輸入
        nth_element(input.begin(), input.begin()+k-1, input.end());         vector<int> result(input.begin(),input.begin()+k); //構造結果向量         return result;
    } };*/ /* /*掌握 方法一:基於partition函式(快排中有用到,stl中也有,但是還是自己實現較好) 多次partition直到樞軸位置為k即可 缺點:會改變輸入陣列的元素位置 平均O(n),每次partition 平均O(n),次數未知,真的是O(n)嗎,存疑? 思考:最好情況為O(n),最壞情況為O(n^2)(如對於倒序排列的陣列) 通過優化partition,比如三數中值樞軸法或隨機初始化樞軸法,可以改善時間複雜度   分析參考: 考慮最壞情況下,每次 partition 將陣列分為長度為 N-1 和 1 的兩部分,然後在長的一邊繼續尋找第 K 大,此時時間複雜度為 O(N^2 )。 不過如果在開始之前將陣列進行隨機打亂,那麼可以儘量避免最壞情況的出現。 而在最好情況下,每次將陣列均分為長度相同的兩半,執行時間 T(N) = N + T(N/2),時間複雜度是 O(N)。 參考:被忽視的 partition 演算法 */ #include <cstdlib> class Solution { public :     vector < int > GetLeastNumbers_Solution ( vector < int > input , int k )     {        if ( input . empty () || k <= 0 || k > input . size ()) return vector < int >(); //處理異常輸入                int left = 0 , right = input . size ()- 1 ;         int pivot_pos ;         while ( left <= right ) //類似二分查詢法         {             pivot_pos = partition ( input , left , right ); //如果要求最大的第k個數,可以對partition函式進行改造             if ( pivot_pos < k - 1 )                 left = pivot_pos + 1 ;             else if ( pivot_pos > k - 1 )                             right = pivot_pos - 1 ;              else                  break ; //此題要求的是返回最小的前k個數,如果僅返回最小的第k個數,直接在這裡return a[pivot_pos]即可                          }         vector < int > result ( input . begin (), input . begin ()+ k ); //構造結果向量         return result ;     } private :     int partition ( vector < int >& a , int left , int right )     {         //隨機初始化樞軸 5ms         //srand(time(nullptr)); //以當前時間為隨機生成器的種子         //int pivotpos = rand()%(right - left + 1) + left; //產生【left,right】之間的數         //swap(a[pivotpos], a[left]); //將樞軸暫時放入起始位置                int pivot = left ; //樞軸位置 4ms                while ( left < right )         {             while ( left < right && a[right] >= a[pivot ]) right --; //找到本次掃描中第一個不滿足樞軸規律的高位數             while ( left < right && a[left] <= a[pivot ]) left ++; //找到本次掃描中第一個不滿足樞軸規律的低位數             swap ( a [ left ], a [ right ]); //交換以使滿足樞軸規律         } //最後結果是left和right均指向樞軸位置         swap ( a [ left ],  a [pivot ]); //將樞軸移動到位         return left ; //返回樞軸位置     } };   // /*掌握 方法二:使用堆或者紅黑樹(平衡二叉搜尋樹) 用容器儲存k個數,遍歷輸入向量過程中不斷更新容器內的數(如果當前數小於容器中的最大值,則插入該數,刪除原最大數) 優點:不需要修改輸入陣列,且適用於處理海量輸入資料 O(nlogk) */ class Solution { public :     vector < int > GetLeastNumbers_Solution ( vector < int > input , int k )     {         if ( input . empty () || k <= 0 || k > input . size ()) return vector < int >(); //處理異常輸入         //仿函式中的greater<T>模板,從大到小排序(預設從小到大,左結點<父結點<根結點)         multiset < int , greater < int >> leastNums ; //用紅黑樹儲存這k個數         for ( int ai : input )         {             if ( leastNums . size () < k ) leastNums . insert ( ai ); //將前k個元素插入容器             else             {                 //第一個數為最大數                 multiset < int , greater < int >>:: iterator greatest_it = leastNums . begin ();                 //如果後續元素小於第一個元素,刪除第一個,加入當前元素                 if ( ai < * greatest_it )                 {                     leastNums . erase ( greatest_it ); //刪除原最大值                     leastNums . insert ( ai ); //插入新元素(logk複雜度)                                     }             }         }                         return vector<int>(leastNums.begin(), leastNums.end()); //返回結果向量(前k個最小的數)     } };   Kth Largest Element in an Array Find the   k th largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element. Example 1: Input: [3,2,1,5,6,4] and k = 2 Output: 5 Example 2: Input: [3,2,3,1,2,4,5,5,6] and k = 4 Output: 4 Note:   You may assume k is always valid, 1 ≤ k ≤ array's length.
C++     暴力法:直接sort,O(nlogn),leetcode用時12ms   /*掌握 方法一:基於partition函式(快排中有用到,stl中也有,但是還是自己實現較好)  多次partition直到樞軸位置為k即可 缺點:會改變輸入陣列的元素位置   leetcode 耗時4ms,若用pivot = left的做法,則耗時20ms 平均O(n),O(1) 每次partition 平均O(n),次數未知,真的是O(n)嗎,存疑? 思考:最好情況為O(n),最壞情況為O(n^2)(如對於倒序排列的陣列) 通過優化partition,比如三數中值樞軸法或隨機初始化樞軸法,可以改善時間複雜度 */ #include <cstdlib> class Solution { public :     int findKthLargest ( vector < int >& a , int k )     {        if ( a . empty () || k <= 0 || k > a . size ()) return 0 ; //處理異常輸入               int left = 0 , right = a . size ()- 1 ;         int pivot_pos ;         while ( left <= right ) //類似二分查詢法         {             pivot_pos = partition ( a , left , right ); //如果要求最大的第k個數,可以對partition函式進行改造             if ( pivot_pos < k - 1 )                 left = pivot_pos + 1 ;             else if ( pivot_pos > k - 1 )                            right = pivot_pos - 1 ;             else                 return a [ pivot_pos ];                         }             } private :     int partition ( vector < int >& a , int left , int right )     {         srand ( time ( nullptr )); //以當前時間為隨機生成器的種子         int pivotpos = rand ()%( right - left + 1 ) + left ; //產生【left,right】之間的數         swap ( a [ pivotpos ], a [ left ]); //將樞軸暫時放入起始位置               int pivot = left ; //樞軸位置               while ( left < right )         {             //改造為從大到小partition,注意符號的變化             while ( left < right && a [right] <= a[pivot ]) right --; //找到本次掃描中第一個不滿足樞軸規律的高位數             while ( left < right && a [ left ] >= a [ pivot ]) left ++; //找到本次掃描中第一個不滿足樞軸規律的低位數             swap ( a [ left ], a [ right ]); //交換以使滿足樞軸規律         } //最後結果是left和right均指向樞軸位置         swap ( a [ left ], a [ pivot ]); //將樞軸移動到位         return left ; //返回樞軸位置     } };   方法二:維護一個堆或者平衡二叉查詢樹儲存這k個數 /*掌握 改進:維護一個大小為k的 小頂堆 ,掃描輸入資料,不斷更新小頂堆的內容 最後堆頂元素即可n個數中第k大的數 leetcode耗時4ms   每次堆調整平均時間複雜度為O(logk),共n次調整,故時間複雜度為O(nlogk) O(nlogk), O(k) */ #include <algorithm> #inlude <queue> class Solution { public :     int findKthLargest ( vector < int >& a , int k )     {         if ( a . empty () || k <= 0 || k > a . size ()) return 0 ; //處理非法輸入(可依題目返回適當值)                 priority_queue < int , vector < int >, greater < int >> minheap ; //構建小頂堆         for ( int i = 0 ; i < a . size (); i ++)         {             if ( i <= k - 1 )                 minheap . push ( a [ i ]); //將前k個元素插入容器             else             {                 if ( a[i] > minheap.top ()) //如果當前元素大於容器中最小的元素,則將該元素push進容器                 {                     minheap . pop ();                     minheap . push ( a [ i ]);//每次堆調整複雜度為O(logk)                 }             }         }         return minheap . top (); //返回堆頂元素,即為n個數中第k大的數(如果要返回前k個數,需將最後的minheap全部pop)           } };   /*瞭解 用stl中make_heap函式,構建大頂堆,然後逐次輸出堆頂元素 (原理與用priority_queue相同,不過沒有額外空間) 用時11ms O(nlogn + klogn),O(1) */ #include <algorithm> class Solution { public :     int findKthLargest ( vector < int >& nums , int k )     {         make_heap ( nums . begin (), nums . end ()); //構建大頂堆, 用nums儲存,與下面區別就是節省了空間         for ( int i = 0 ; i < k - 1 ; i ++)         {             pop_heap ( nums . begin (), nums . end ()); //將堆頂元素移至末尾,重新調整使(begin~end-1)的元素滿足堆規律             nums . pop_back (); //移除末尾元素         }                 return nums [ 0 ];                         } };   //用stl中sort函式,用時12ms /* #include <algorithm> class Solution { public:     int findKthLargest(vector<int>& nums, int k)     {         sort(nums.rbegin(), nums.rend()); //nums.rbegin()返回指向容器最後元素的逆向迭代器(因為sort預設按從小到大排序),         //sort將rbegin()指向位置當做第一個元素,故可以實現從大到小排序         return nums[k-1]; //第k個數,注意這裡索引為k-1             } }; */ /* //用stl中nth_element函式。?為什麼沒有sort()或者堆排序快 //用時14ms #include <algorithm> class Solution { public:        int findKthLargest(vector<int>& nums, int k)     {         //nth_element(nums.begin(), nums.begin()+k-1, nums.end(),customMore);         nth_element(nums.begin(), nums.begin()+k-1, nums.end(),greater<int>()); //原理為快排         //這裡直接用STL裡的函式,比較函式設定為greater(預設為小數在前),注意中間(k-1)表示第k個最大的數         return nums[k-1]; //第k個數,注意這裡索引為k-1             } }; */ /*     // 用自定義函式物件排序     struct     {         bool operator()(int a, int b) const         {               return a > b;         }       }customMore; */   相關:

Top K問題的兩種解決思路

Top K問題在資料分析中非常普遍的一個問題(在面試中也經常被問到),比如:
從20億個數字的文字中,找出最大的前100個。
解決Top K問題有兩種思路,
  • 最直觀:小頂堆(大頂堆 -> 最小100個數), 該方法沒有修改輸入資料,且非常適合海量資料的輸入,不用一次性讀入記憶體,可以藉助硬碟一邊讀一邊處理,平均時間複雜度為O(nlogk)。
  • 較高效:基於partition函式的解法, 平均時間複雜度為O(n),但是會修改輸入陣列。
Quick Select(用快排中的partition函式)的目標是找出第k大元素,所以
  • 若切分後的左子陣列的長度 > k,則第k大元素必出現在左子陣列中;
  • 若切分後的左子陣列的長度 = k-1,則第k大元素為pivot;
  • 若上述兩個條件均不滿足,則第k大元素必出現在右子陣列中。
https://www.cnblogs.com/en-heng/p/6336625.html   quick select實質是分治法 分治法 ,將1億個資料分成100份,每份100萬個資料,找到每份資料中最大的10000個,最後在剩下的100*10000個數據裡面找出最大的10000個。如果100萬資料選擇足夠理想,那麼可以過濾掉1億資料裡面99%的資料。100萬個資料裡面查詢最大的10000個數據的方法如下:用快速排序的方法,將資料分為2堆,如果大的那堆個數N大於10000個,繼續對大堆快速排序一次分成2堆,如果大的那堆個數N大於10000個,繼續對大堆快速排序一次分成2堆,如果大堆個數N小於10000個,就在小的那堆裡面快速排序一次,找第10000-n大的數字;遞迴以上過程,就可以找到第1w大的數。參考上面的找出第1w大數字,就可以類似的方法找到前10000大數字了。此種方法需要每次的記憶體空間為10^6*4=4MB,一共需要101次這樣的比較 https://blog.csdn.net/zyq522376829/article/details/47686867