从零学算法215

13 篇文章 0 订阅
9 篇文章 0 订阅
本文介绍了如何在O(n)的时间复杂度内找出整数数组中的第k个最大元素,方法包括使用排序、最小堆、优先队列、快速排序优化(三路划分)和手写大根堆。提供了多种解决方案和相应的代码示例。
摘要由CSDN通过智能技术生成

215.给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

  • 直接排序取倒数第 k 个元素就不多说什么了,面试官直接沉默
  •   public int findKthLargest(int[] nums, int k) {
          Arrays.sort(nums);
          return nums[nums.length-k];
      }
    
  • 这里用最小堆也可以,当堆长度大于 k 时就移除堆顶元素,最后的堆顶元素就是解
  •   public int findKthLargest(int[] nums, int k) {
          PriorityQueue<Integer> queue = new PriorityQueue<>();
          for(int n:nums){
              queue.add(n);
              if(queue.size() > k)queue.poll();
          }
          return queue.peek();
      }
    
  • 快速排序(超时):借鉴快排的思路,降序排序,如果快排时的基准数 mid 为第 k 大的数就直接返回
  •   public int findKthLargest(int[] nums, int k) {
          int len = nums.length;
          int left=0,right = len-1;
          while(left<=right){
              int mid=left;
              // 将数组分为两部分 [left,mid] 都大于 nums[mid]
              // [mid,right] 都小于 nums[mid]
              for(int j=mid+1;j<=right;j++){
                  if(nums[j]<nums[left])swap(nums,j,++mid);
              }
              swap(nums,left,mid);
              // 记得降序数组下标为 0 的数为第 1 大的数,所以要 +1
              if(mid+1==k)return nums[mid];
              // 降序数组为[第1大,第2大,第3大...]
             	// 比如是要第 3 大的,但是现在拿到的 mid 对应第 2 大的,那我们要的值就在右半部分
             	// 所以更新 left
              else if(mid+1<k)left=mid+1;
              // 同理
              else right=mid-1;
          }
          return -1;
      }
      public void swap(int[] nums,int i,int j){
          if(i!=j){
              nums[i]^=nums[j];
              nums[j]^=nums[i];
              nums[i]^=nums[j];
          }
      }
    
  • 快排优化:上述情况可能会超时,是因为当数组中包含大量重复元素,每次都划分出长度为 1 和 n-1 的两个部分,那时间复杂度就退化至 O(N2),所以我们可以采用“三路划分”的方式,每次分出大于,等于以及小于基准数的三个数组,如果 k 在等于基准数的部分中就直接返回,为了进一步提升算法的稳健性,我们采用随机选择的方式来选定基准数。
  • 主要说一下当 k 在小于基准数部分的划分思路,画个图就很好理解判断 k 是否在小于的部分
  • 由于 k 是表示第 k 大的数,所以我们降序展示三部分,这样 k 在坐标轴中从前往后展示时为第 1,2,…,k
    请添加图片描述
  • n: nums.size()
  • 由图可知,当 k 处于 small 部分时,n-small.size()<k
  • 然后更新 k,k 在 small 部分中是第几大的,把 n-small.size 看做第 1 大的,此时的 k 其实就是原本的 k 相较于 n-small.size 长了多少,即 k-(n-small.size())=k-n+small.size(),同理,你也可以写成 k-big.size-equal.size
  •   private int quickSelect(List<Integer> nums, int k) {
          // 随机选择基准数
          Random rand = new Random();
          int pivot = nums.get(rand.nextInt(nums.size()));
          // 存放大于等于小于基准数的三部分
          List<Integer> big = new ArrayList<>();
          List<Integer> equal = new ArrayList<>();
          List<Integer> small = new ArrayList<>();
          for(int n:nums){
              if(n>pivot)big.add(n);
              else if(n<pivot)small.add(n);
              else equal.add(n);
          }
          // 这个很好理解,big.size()>=k 说明第 1,2...,k 大的数被包含其中
          if(big.size()>=k)return quickSelect(big,k);
          // 当 k 在小于基准数部分的划分
          else if(nums.size()-small.size()<k)
          	  return quickSelect(small,k - nums.size() + small.size());
          else return pivot;
      }
    
      public int findKthLargest(int[] nums,int k){
          List<Integer> list = new ArrayList<>();
          for(int n:nums){
              list.add(n);
          }
          return quickSelect(list,k);
      }
    
  • 手写大根堆:直接找了官方题解,数组转成大根堆后删除 k-1 次堆顶元素,此时堆顶元素就是答案
  •   public int findKthLargest(int[] nums, int k) {
          int heapSize = nums.length;
          buildMaxHeap(nums, heapSize);
          for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
              swap(nums, 0, i);
              --heapSize;
              maxHeapify(nums, 0, heapSize);
          }
          return nums[0];
      }
    
      public void buildMaxHeap(int[] a, int heapSize) {
          for (int i = heapSize / 2; i >= 0; --i) {
              maxHeapify(a, i, heapSize);
          } 
      }
    
      public void maxHeapify(int[] a, int i, int heapSize) {
          int l = i * 2 + 1, r = i * 2 + 2, largest = i;
          if (l < heapSize && a[l] > a[largest]) {
              largest = l;
          } 
          if (r < heapSize && a[r] > a[largest]) {
              largest = r;
          }
          if (largest != i) {
              swap(a, i, largest);
              maxHeapify(a, largest, heapSize);
          }
      }
    
      public void swap(int[] a, int i, int j) {
          int temp = a[i];
          a[i] = a[j];
          a[j] = temp;
      }
    
  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值