算法多解——JZ40 最小的K个数(大根堆模拟及手撕)

题面

算法多解——JZ40 最小的K个数(大根堆模拟及手撕)_第1张图片

算法多解——JZ40 最小的K个数(大根堆模拟及手撕)_第2张图片


解法1(快排)

复杂度

时间复杂度:O(nlongn),取决于排序的快慢

空间复杂度:O(n)

思路

  • 由于逻辑关系和常理,k
  • 要求最小的k个数,最能直接想到的思路就是:
  • 先将这些数排好序,前k个最小的数压入ans数组,最后返回就好了。
  • 根据常用习惯,下面就用了sort快排和Vector的push_back解决了。

也可以排序后直接 return vector({input.begin(), input.begin()+k});

算法多解——JZ40 最小的K个数(大根堆模拟及手撕)_第3张图片

代码

class Solution {
public:
    vector GetLeastNumbers_Solution(vector input, int k) {
        vector ans; //开辟存放结果的数组
        sort(input.begin(),input.end());
        for(int i=0; i

解法2(STL进阶:大根堆)

算法多解——JZ40 最小的K个数(大根堆模拟及手撕)_第4张图片

复杂度

时间复杂度:O(nlongk),堆的每次操作时间复杂度不超过O(logk)

空间复杂度:O(k)

思路

  • 优先队列模拟大根堆来维护最小的k个数字
  • 有很多排序算法,我们很容易想到通过选择复杂度更低的排序算法来优化我们的解法
  • 这里就要用到大根堆维护最小的k个数字:
  • 依次将n个数字放入堆中,若堆中数字超过k个,就说明此时堆中最大的数一定不属于前k个最小数,不断将堆顶元素出堆,就能得到想要的结果了。

大小根堆的介绍:

算法多解——JZ40 最小的K个数(大根堆模拟及手撕)_第5张图片

代码

class Solution {
public:
  priority_queue q;
  vector GetLeastNumbers_Solution(vector input, int k) {
      // 将原数组的数放入优先队列
      for(auto x:input){
          q.push(x);
          if(q.size()>k) q.pop();
      }
      input.clear();
      //若达不到k个数字
      if(q.size()

手写大根堆代码

public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList list = new ArrayList<>();
        if(input==null || input.length==0 || k>input.length || k==0)
            return list;
        int[] arr = new int[k+1];//数组下标0的位置作为哨兵,不存储数据
        //初始化数组
        for(int i=1; i 0; i--) {
            adjustDown(arr, i, arr.length);
        }
    }

    // 堆排序中对一个子二叉树进行堆排序 
    public void adjustDown(int[] arr, int k, int length) {
        arr[0] = arr[k];//哨兵
        for (int i=2*k; i<=length; i*=2) {
            if(ilength-1 || arr[0]>=arr[i])
                break;
            else {
                arr[k] = arr[i];
                k = i; //向下筛选
            }
        }
        arr[k] = arr[0];
    }
};

解法3(快速选择算法

复杂度

时间复杂度:期望O(n),n为数组长度,最坏时间复杂度为O(n^2)
空间复杂度:期望O(logn),递归调用的期望深度为logn,但最坏情况为O(n)

思路

  1. 根据题目可知,返回的数组不必是有序的,故我们可以想到快速选择算法来解决本问题,不熟悉快速选择算法的可以先看看第k个数这题,与其思路一致,我们可以利用快速排序的思想来实现
  2. 快速排序每次根据一个pivot点对区间进行划分,使得pivot左侧的数都比它小,右侧的数都比它大,然后再对两部分就行划分,而快速选择只需要选择其中一部分进行划分即可,根据k可以选择进入那个递归入口,这样的期望时间复杂度是线性的,但最坏情况下是O(n^2)的
  3. 算法步骤,首先选择数组中点为pivot,再使用双指针算法交换左侧大于等于pivot的数和右边小于等于pivot的数,最后我们可以保证数组被分为了以pivot划分的两部分,接着根据k选择递归入口即可,算法结束后我们可以保证数组的前k个数是最小的k个数,但是并不是升序的

代码

class Solution {
public:
    vector GetLeastNumbers_Solution(vector input, int k) {
        quickSelect(input, 0, input.size() - 1, k);
        return vector(input.begin(), input.begin() + k);
    }

    void quickSelect(vector& input, int l, int r, int k){
        if(l >= r) return;
        int i = l - 1, j = r + 1, pivot = input[l + r >> 1];
        // 令小于pivot的数都在左侧, 大于pivot的数都在右侧
        while(i < j){
            while(input[++i] < pivot);
            while(input[--j] > pivot);
            if(i < j) swap(input[i], input[j]);
        }
        int cnt = j - l + 1;    // cnt表示小于等于pivot的数的数量
        if(cnt >= k){    // 若cnt >= k, 说明答案在左区间
            return quickSelect(input, l, j, k);
        }
        return quickSelect(input, j + 1, r, k - cnt);    // 左边cnt个数已经确定, 只要确定右边的即可
    }
};

你可能感兴趣的:(力扣找手感,C++,数据结构与算法,stl,c++,算法,leetcode)