leetcode 剑指 Offer 40. 最小的k个数

【题目】剑指 Offer 40. 最小的k个数

剑指 Offer 40. 最小的k个数
*215. 数组中的第K个最大元素
*347. 前 K 个高频元素

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000

【解题思路1】排序,返回前k个

class Solution {
     
    public int[] getLeastNumbers(int[] arr, int k) {
     
        if(k==0 || arr==null){
     
            return new int[0];
        }
        if(arr.length <= k){
     
            return arr;
        }

        Arrays.sort(arr);
        int[] ans = new int[k];
        for(int i=0; i<k; i++){
     
            ans[i] = arr[i];
        }
        return ans;
    }
}

【解题思路2】计数数组

如果数的上限不是很大可以这么做

class Solution {
     
    public int[] getLeastNumbers(int[] arr, int k) {
     
        if (k == 0 || arr.length == 0) {
     
            return new int[0];
        }
        int[] count = new int[10001];
        for (int i: arr) {
     
            count[i]++;
        }

        int[] res = new int[k];
        int idx = 0;
        for (int i = 0; i < count.length; i++) {
     
            while (count[i]-- > 0 && idx < k) {
     
                res[idx] = i;
                idx++;
            }
            if (idx == k) {
     
                break;
            }
        }
        return res;
    }
}

【解题思路3】堆排序

可以使用Java 中提供了现成的优先队列 PriorityQueue,或者自己手动建堆
用一个容量为 K 的大根堆,每次 poll 出最大的数,那堆中保留的就是前 K 小

保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:

  1. 若目前堆的大小小于K,将当前数字放入堆中。
  2. 否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶还大,这个数就直接跳过;反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。
class Solution {
     
    public int[] getLeastNumbers(int[] arr, int k) {
     
        if(k == 0 || arr.length == 0)   return new int[]{
     };

        // 默认是小根堆,实现大根堆需要重写一下比较器
        Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
        for(int num : arr) {
     
            if(pq.size() < k) {
     
                pq.offer(num);
            } else if(num < pq.peek()) {
     
                pq.poll();
                pq.offer(num);
            }
        }

        int[] res = new int[k];
        for(int i = 0; i < k; i++) {
     
            res[i] = pq.poll();
        }
        // for(int num: pq) {
     
        //     res[idx++] = num;
        // }
        return res;
    }
}

【解题思路4】快排

找前 K 大/前 K 小问题不需要对整个数组进行 O(NlogN) 的排序,直接通过快排切分排好第 K 小的数(下标为 K-1),那么它左边的数就是比它小的另外 K-1 个数。

class Solution {
     
    public int[] getLeastNumbers(int[] arr, int k) {
     
        if(k == 0 || arr.length == 0)   return new int[]{
     };
        if(arr.length <= k) return arr;
        return quickSearch(arr, 0, arr.length - 1, k);
    }

    // 快排
    public int[] quickSearch(int[] nums, int left, int right, int k) {
     
        int j = partition(nums, left, right);
        // 这次划分刚好下标是k,那说明从头到j位置刚好是要找的最小的k个元素
        if(j == k) {
     
            return Arrays.copyOf(nums, j);
        }

        // 根据j的大小确定继续细分左半部分或者右半部分
        return j > k ? quickSearch(nums, left, j - 1, k) : quickSearch(nums, j + 1, right, k);
    }

    // 划分,返回下标j,是的比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边
    public int partition(int[] nums, int left, int right) {
     
        int pivot = nums[left]; // 选择区间的最左端元素作为基准
        while(left < right) {
     
            while(left < right && nums[right] >= pivot) right--;
            nums[left] = nums[right];
            while(left < right && nums[left] <= pivot)  left++;
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
    }
}

你可能感兴趣的:(#,最小/最大的第k个数,#,堆,剑指offer,leetcode,堆排序)