leetcode常規算法題覆盤(第十六期)——數據流中的第 K 大元素

題目原文css

703. 數據流中的第 K 大元素

設計一個找到數據流中第 k 大元素的類(class)。注意是排序後的第 k 大元素,不是第 k 個不一樣的元素。java

請實現 KthLargest 類:python

  • KthLargest(int k, int[] nums) 使用整數 k 和整數流 nums 初始化對象。
  • int add(int val)val 插入數據流 nums 後,返回當前數據流中第 k 大的元素。

 

示例:面試

輸入:
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
輸出:
[null, 4, 5, 5, 8, 8]

解釋:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3);   // return 4
kthLargest.add(5);   // return 5
kthLargest.add(10);  // return 5
kthLargest.add(9);   // return 8
kthLargest.add(4);   // return 8

 

提示:
  • 1 <= k <= 104
  • 0 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • -104 <= val <= 104
  • 最多調用 add 方法 104
  • 題目數據保證,在查找第 k 大元素時,數組中至少有 k 個元素

嘗試解答算法

寒假月期間的力扣主題是滑動窗口,大部分題目比較弟弟,但也有個別題目或新穎清奇或夯實基礎,例如這道題雖然是簡單難度,可是其主要考察的內容是經典的topK,topK的標準解法通常是寫一個堆數據結構,博主仍是太菜了沒有想到這種解法,解答費了很多時間,最後檢驗一共十個測試用例,卡在了測試時間複雜度的第七個超長用例沒有AC,上垃圾代碼:數組

 1 class KthLargest {
 2     public int k;
 3     public int[] nums;
 4     public KthLargest(int k, int[] nums) {
 5         this.k = k;
 6         Arrays.sort(nums);
 7         this.nums = nums;
 8         // for(int i=0;i<nums.length;i++){
 9         //     System.out.print(this.nums[i]);
10         // }
11     }
12     
13     public int add(int val) {
14         int[] new_nums = new int[nums.length+1];
15         boolean vias = false;
16         int new_index = 0;
17         int index = 0;
18         while(new_index<new_nums.length && index<nums.length){
19             if(val>nums[index]){
20                 new_nums[new_index] = nums[index];
21             }
22             else{
23                 if(!vias){
24                     new_nums[new_index] = val;
25                     index--;
26                     vias = true;
27                 }
28                 else{
29                     new_nums[new_index] = nums[index];
30                 }
31             }
32             new_index++;
33             index++;
34         }
35         if(!vias){
36             new_nums[new_nums.length-1] = val;
37         }
38         for(int i=0;i<new_nums.length;i++){
39             System.out.print(new_nums[i]);
40             System.out.print(' ');
41         }
42         System.out.println();
43         this.nums = new_nums;
44         return new_nums[new_nums.length-k];
45     }
46 }
47 
48 /**
49  * Your KthLargest object will be instantiated and called as such:
50  * KthLargest obj = new KthLargest(k, nums);
51  * int param_1 = obj.add(val);
52  */

標準題解數據結構

方法一:使用heapq庫

 1 class KthLargest:
 2 
 3     def __init__(self, k: int, nums: List[int]):
 4         self.heap = []
 5         self.k = k
 6         for num in nums:
 7             heapq.heappush(self.heap,num)
 8             if len(self.heap) > k:
 9                 heapq.heappop(self.heap)
10 
11 
12     def add(self, val: int) -> int:
13         heapq.heappush(self.heap,val)
14         if len(self.heap) > self.k:
15             heapq.heappop(self.heap)
16         return self.heap[0]
17 
18 
19 ##做者:MiloMusiala
20 ##連接:https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/solution/python-dong-hua-shou-xie-shi-xian-dui-by-ypz2/
21 ##來源:力扣(LeetCode)
22 ##著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

方法二:手寫實現

咱們經過本身手寫來詳細刨析一下堆這個數據結構究竟是怎麼實現的。
堆的特色:

    內部數據是有序的
    能夠彈出堆頂的元素,大頂堆就是彈出最大值,小頂堆就是彈出最小值
    每次加入新元素或者彈出堆頂元素後,調整堆使之從新有序僅須要O(logn)的時間
    支持在線算法

堆的本質:

    它是一個徹底二叉樹
    實現的時候咱們不須要建造一個樹,改用一個數組便可

    那麼咱們是如何把一個徹底二叉樹和一個數組關聯到一塊兒的呢?
    給樹的節點編號,節點的編號就是元素在數組中的下標
    幻燈片1.JPG

    因而咱們發現一個很重要的結論:
    已知一個節點的編號爲index,那麼它的父節點的編號爲:

    father_index=⌊index−12⌋father\_index = \lfloor {index-1 \over 2}\rfloor father_index=⌊2index−1​⌋
    左孩子節點的編號爲

    left_index=index∗2+1left\_index = index * 2 + 1 left_index=index∗2+1
    右孩子節點的編號爲

    right_index=index∗2+2right\_index = index * 2 + 2 right_index=index∗2+2

如何調整堆

    1.添加元素

    把新數據添加到樹的最後一個元素,也就是數組的末尾
    把末尾節點向上調整app

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  1. 彈出堆頂
  • 交換根節點與最後一個節點的值
  • 刪除最後一個節點
  • 把根節點向下調整

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

因此須要寫的函數有

一、初始化函數

1     def __init__(self,desc=False):
2         """
3         初始化,默認建立一個小頂堆
4         """
5         self.heap = []
6         self.desc = desc

二、堆的大小測試

    @property
    def size(self):
        return len(self.heap)

 

 三、返回堆頂元素

1     def top(self):
2         if self.size:
3             return self.heap[0]
4         return None

四、添加元素

1     def push(self,item):
2         """
3         添加元素
4         第一步,把元素加入到數組末尾
5         第二步,把末尾元素向上調整
6         """
7         self.heap.append(item)
8         self._sift_up(self.size-1)

五、彈出堆頂元素

 1     def pop(self):
 2         """
 3         彈出堆頂
 4         第一步,記錄堆頂元素的值
 5         第二步,交換堆頂元素與末尾元素
 6         第三步,刪除數組末尾元素
 7         第四步,新的堆頂元素向下調整
 8         第五步,返回答案
 9         """
10         item = self.heap[0]
11         self._swap(0,self.size-1)
12         self.heap.pop()
13         self._sift_down(0)
14         return item

 

六、判斷兩個元素的大小關係,這裏有個小trick

 

1     def _smaller(self,lhs,rhs):
2         return lhs > rhs if self.desc else lhs < rhs
3 
4 
5 做者:MiloMusiala
6 連接:https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/solution/python-dong-hua-shou-xie-shi-xian-dui-by-ypz2/
7 來源:力扣(LeetCode)
8 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

 

 

七、向上調整

 1     def _sift_up(self,index):
 2         """
 3         向上調整
 4         若是父節點和當前節點知足交換的關係
 5         (對於小頂堆是父節點元素更大,對於大頂堆是父節點更小),
 6         則持續將當前節點向上調整
 7         """
 8         while index:
 9             parent = (index-1) // 2
10             
11             if self._smaller(self.heap[parent],self.heap[index]):
12                 break
13                 
14             self._swap(parent,index)
15             index = parent

八、向下調整

 1     def _sift_down(self,index):
 2         """
 3         向下調整
 4         若是子節點和當前節點知足交換的關係
 5         (對於小頂堆是子節點元素更小,對於大頂堆是子節點更大),
 6         則持續將當前節點向下調整
 7         """
 8         # 若存在子節點
 9         while index*2+1 < self.size:
10             smallest = index
11             left = index*2+1
12             right = index*2+2
13             
14             if self._smaller(self.heap[left],self.heap[smallest]):
15                 smallest = left
16                 
17             if right < self.size and self._smaller(self.heap[right],self.heap[smallest]):
18                 smallest = right
19                 
20             if smallest == index:
21                 break
22 
23             self._swap(index,smallest)
24             index = smallest

九、交換兩個元素

1     def _swap(self,i,j):
2         self.heap[i],self.heap[j] = self.heap[j],self.heap[i]
3 
4 
5 做者:MiloMusiala
6 連接:https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/solution/python-dong-hua-shou-xie-shi-xian-dui-by-ypz2/
7 來源:力扣(LeetCode)
8 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

 

完整代碼:

  1 class Heap:
  2     def __init__(self,desc=False):
  3         """
  4         初始化,默認建立一個小頂堆
  5         """
  6         self.heap = []
  7         self.desc = desc
  8     
  9     @property
 10     def size(self):
 11         return len(self.heap)
 12     
 13     def top(self):
 14         if self.size:
 15             return self.heap[0]
 16         return None
 17     
 18     def push(self,item):
 19         """
 20         添加元素
 21         第一步,把元素加入到數組末尾
 22         第二步,把末尾元素向上調整
 23         """
 24         self.heap.append(item)
 25         self._sift_up(self.size-1)
 26     
 27     def pop(self):
 28         """
 29         彈出堆頂
 30         第一步,記錄堆頂元素的值
 31         第二步,交換堆頂元素與末尾元素
 32         第三步,刪除數組末尾元素
 33         第四步,新的堆頂元素向下調整
 34         第五步,返回答案
 35         """
 36         item = self.heap[0]
 37         self._swap(0,self.size-1)
 38         self.heap.pop()
 39         self._sift_down(0)
 40         return item
 41     
 42     def _smaller(self,lhs,rhs):
 43         return lhs > rhs if self.desc else lhs < rhs
 44     
 45     def _sift_up(self,index):
 46         """
 47         向上調整
 48         若是父節點和當前節點知足交換的關係
 49         (對於小頂堆是父節點元素更大,對於大頂堆是父節點更小),
 50         則持續將當前節點向上調整
 51         """
 52         while index:
 53             parent = (index-1) // 2
 54             
 55             if self._smaller(self.heap[parent],self.heap[index]):
 56                 break
 57                 
 58             self._swap(parent,index)
 59             index = parent
 60     
 61     def _sift_down(self,index):
 62         """
 63         向下調整
 64         若是子節點和當前節點知足交換的關係
 65         (對於小頂堆是子節點元素更小,對於大頂堆是子節點更大),
 66         則持續將當前節點向下調整
 67         """
 68         # 若存在子節點
 69         while index*2+1 < self.size:
 70             smallest = index
 71             left = index*2+1
 72             right = index*2+2
 73             
 74             if self._smaller(self.heap[left],self.heap[smallest]):
 75                 smallest = left
 76                 
 77             if right < self.size and self._smaller(self.heap[right],self.heap[smallest]):
 78                 smallest = right
 79                 
 80             if smallest == index:
 81                 break
 82             
 83             self._swap(index,smallest)
 84             index = smallest
 85     
 86     def _swap(self,i,j):
 87         self.heap[i],self.heap[j] = self.heap[j],self.heap[i]
 88 
 89 class KthLargest:
 90 
 91     def __init__(self, k: int, nums: List[int]):
 92         self.heap = Heap()
 93         self.k = k
 94         for num in nums:
 95             self.heap.push(num)
 96             if self.heap.size > k:
 97                 self.heap.pop()
 98 
 99 
100     def add(self, val: int) -> int:
101         self.heap.push(val)
102         if self.heap.size > self.k:
103             self.heap.pop()
104         return self.heap.top()
105 
106 
107 
108 ##做者:MiloMusiala
109 ##連接:https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/solution/python-dong-hua-shou-xie-shi-xian-dui-by-ypz2/
110 ##來源:力扣(LeetCode)
111 ##著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

 

 

思路差距

topK問題在各大互聯網面試題目中比較常見,通常是往一串有序數據中頻繁執行插入、刪除的操做,爲優化此類操做,通常都會使用堆這類數據結構,各語言對於堆都有現成的類,好比java語言的PriorityQueue,python語言的heapq,菜雞博主認爲如果爲了提升代碼水平,應該作到理解堆的原理並能手撕堆數據結構。

技術差距

手撕堆數據結構、堆排序還需練習

相關文章
相關標籤/搜索