• leetcode常规算法题复盘(第十六期)——数据流中的第 K 大元素


    题目原文

    703. 数据流中的第 K 大元素

    设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

    请实现 KthLargest 类:

    • 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} floor 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.添加元素

        把新数据添加到树的最后一个元素,也就是数组的末尾
        把末尾节点向上调整

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    1. 弹出堆顶
    • 交换根节点与最后一个节点的值
    • 删除最后一个节点
    • 把根节点向下调整

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    所以需要写的函数有

    1、初始化

    1     def __init__(self,desc=False):
    2         """
    3         初始化,默认创建一个小顶堆
    4         """
    5         self.heap = []
    6         self.desc = desc

    2、堆的大小

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

     

     3、返回堆顶元素

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

    4、添加元素

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

    5、弹出堆顶元素

     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

     

    6、判断两个元素的大小关系,这里有个小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 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

     

     

    7、向上调整

     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

    8、向下调整

     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

    9、交换两个元素

    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,菜鸡博主认为若是为了提高代码水平,应该做到理解堆的原理并能手撕堆数据结构。

    技术差距

    手撕堆数据结构、堆排序还需练习

     

  • 相关阅读:
    扩展DigitalClock显示日期+时间
    利用Handler定时更新Android UI
    CheckBox在表格中全选、部分选和反选
    jQuery实现表格间隔色
    Android中对话框(dialog)的使用
    The connection to adb is down, and a severe error has occured.
    struts2类型转化
    Android开发之旅:环境搭建及HelloWorld
    C/C++浮点数在内存中的存储方式
    用标签写登录界面
  • 原文地址:https://www.cnblogs.com/monkiki/p/14471764.html
Copyright © 2020-2023  润新知