前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >剑指offer | 面试题32:最小的k个数

剑指offer | 面试题32:最小的k个数

作者头像
千羽
发布2021-12-29 13:27:51
3630
发布2021-12-29 13:27:51
举报
文章被收录于专栏:程序员千羽

死磕算法系列文章

  1. 干货 | 手撕十大经典排序算法
  2. 剑指offer | 认识面试
  3. 剑指offer | 面试题2:实现Singleton模式
  4. 剑指offer | 面试题3:二维数组的查找
  5. 剑指offer | 面试题4:替换空格
  6. 剑指offer | 面试题5:从尾到头打印链表
  7. 剑指offer | 面试题6:重建二叉树
  8. 剑指offer | 面试题7:用两个栈实现队列
  9. 剑指offer | 面试题8:旋转数组的最小数字
  10. 剑指offer | 面试题9:斐波那契数列
  11. 剑指offer | 面试题10:青蛙跳台阶问题
  12. 剑指offer | 面试题11:矩阵覆盖
  13. 剑指offer | 面试题12:二进制中1的个数
  14. 剑指offer | 面试题13:数值的整数次方
  15. 剑指offer | 面试题14:打印从1到最大的n位数
  16. 剑指offer | 面试题15:删除链表的节点
  17. 剑指offer | 面试题16:将数组中的奇数放在偶数前
  18. 剑指offer | 面试题17:链表中倒数第k个节点
  19. 剑指offer | 面试题18:反转链表
  20. 剑指offer | 面试题19:合并两个有序链表
  21. 剑指offer | 面试题20:判断二叉树A中是否包含子树B
  22. 剑指offer | 面试题21:二叉树的镜像
  23. 剑指offer | 面试题22:顺时针打印矩阵
  24. 剑指offer | 面试题23:包含min函数的栈
  25. 剑指offer | 面试题24:栈的压入、弹出序列
  26. 剑指offer | 面试题25:从上到下打印二叉树
  27. 剑指offer | 面试题26:二叉搜索树的后序遍历序列
  28. 剑指offer | 面试题27:二叉树中和为某一值的路径
  29. 剑指offer | 面试题28:复杂链表的复制
  30. 剑指offer | 面试题29:二叉搜索树转换为双向链表
  31. 剑指offer | 面试题30:字符串的排列
  32. 剑指offer | 面试题31:数组中出现次数超过一半的数字

Leetcode : https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/

GitHub : https://github.com/nateshao/leetcode/blob/main/algo-notes/src/main/java/com/nateshao/sword_offer/topic_32_getLeastNumbers/Solution.java

最小的k个数

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

难度:简单

示例 1:

代码语言:javascript
复制
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例 2:

代码语言:javascript
复制
输入:arr = [0,1,2,1], k = 1
输出:[0]

方法一:快排

本题使用排序算法解决最直观,对数组 arr 执行排序,再返回前 k 个元素即可。使用任意排序算法皆可。

快速排序原理:

快速排序算法有两个核心点,分别为 “哨兵划分” 和 “递归” 。

哨兵划分操作: 以数组某个元素(一般选取首元素)为 基准数 ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。

“如下图所示,为哨兵划分操作流程。通过一轮 哨兵划分 ,可将数组排序问题拆分为 两个较短数组的排序问题 (本文称之为左(右)子数组)。

递归:左子数组右子数组 递归执行 哨兵划分,直至子数组长度为 1 时终止递归,即可完成对整个数组的排序。

复杂度分析:

  • 时间复杂度O(N logN): 库函数、快排等排序算法的平均时间复杂度为O(N log N)。
  • 空间复杂度O(N) : 快速排序的递归深度最好(平均)为O(logN),最差情况(即输入数组完全倒 序)为O(N)。
代码语言:javascript
复制
package com.nateshao.sword_offer.topic_32_getLeastNumbers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Queue;

/**
 * @date Created by 邵桐杰 on 2021/12/5 19:48
 * @微信公众号 程序员千羽
 * @个人网站 www.nateshao.cn
 * @博客 https://nateshao.gitee.io
 * @GitHub https://github.com/nateshao
 * @Gitee https://gitee.com/nateshao
 * Description: 剑指 Offer 40. 最小的k个数
 * 题目描述:输入 n 个整数,找出其中最小的 K 个数。
 * 思路:先将前 K 个数放入数组,进行堆排序,若之后的数比它还小,则进行调整
 */
public class Solution {
    public static void main(String[] args) {
        int[] input = {4, 5, 1, 6, 2, 7, 3, 8};
        int k = 4;
        ArrayList<Integer> list = GetLeastNumbers_Solution(input, k);
        list.stream().forEach(lists -> System.out.print(lists + " "));
        System.out.println("======================");
        int[] numbers = getLeastNumbers(input, k);
        for (int number : numbers) {
            System.out.print(number + " ");
        }
    }

    /***
     * 方法一:快排
     * @param arr
     * @param k
     * @return
     */
    public static int[] getLeastNumbers(int[] arr, int k) {
        quickSort(arr, 0, arr.length - 1);
        return Arrays.copyOf(arr, k);
    }

    private static void quickSort(int[] arr, int leftIndex, int rightIndex) {
        // 子数组长度为 1 时终止递归
        if (leftIndex >= rightIndex) return;
        // 哨兵划分操作(以 arr[left] 作为基准数)
        int left = leftIndex, right = rightIndex;
        while (left < right) {
            while (left < right && arr[right] >= arr[leftIndex]) right--;
            while (left < right && arr[left] <= arr[leftIndex]) left++;
            swap(arr, left, right);
        }
        swap(arr, left, leftIndex);
        // 递归左(右)子数组执行哨兵划分
        quickSort(arr, leftIndex, left - 1);
        quickSort(arr, left + 1, rightIndex);
    }

    private static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    /**************************  堆  ********************************/
    /**
     * 方法二:
     * 保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:
     * 1. 若目前堆的大小小于K,将当前数字放入堆中。
     * 2. 否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶还大,这个数就直接跳过;
     *    反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。
     * @param arr
     * @param k
     * @return
     */
    public int[] getLeastNumbers2(int[] arr, int k) {
        if (k == 0 || arr.length == 0) return new int[0];
        // 默认是小根堆,实现大根堆需要重写一下比较器。
        Queue<Integer> queue = new PriorityQueue<>((v1, v2) -> v2 - v1);
        for (int num: arr) {
            if (queue.size() < k) {
                queue.offer(num);
            } else if (num < queue.peek()) {
                queue.poll();
                queue.offer(num);
            }
        }

        // 返回堆中的元素
        int[] res = new int[queue.size()];
        int idx = 0;
        for(int num: queue) {
            res[idx++] = num;
        }
        return res;
    }
    /******************** 计数排序 ***********************/
    public int[] getLeastNumbers3(int[] arr, int k) {
        if (k == 0 || arr.length == 0) return new int[0];

        // 统计每个数字出现的次数
        int[] counter = new int[10001];
        for (int num: arr) {
            counter[num]++;
        }
        // 根据counter数组从头找出k个数作为返回结果
        int[] res = new int[k];
        int idx = 0;
        for (int num = 0; num < counter.length; num++) {
            while (counter[num]-- > 0 && idx < k) {
                res[idx++] = num;
            }
            if (idx == k) break;
        }
        return res;
    }

    /**************************  剑指offer  *********************/
    /**
     * 大根堆(前 K 小) / 小根堆(前 K 大)
     * @param input
     * @param k
     * @return
     */
    public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if (input == null || k <= 0 || k > input.length) {
            return list;
        }
        int[] kArray = Arrays.copyOfRange(input, 0, k);
        // 创建大根堆
        buildHeap(kArray);
        for (int i = k; i < input.length; i++) {
            if (input[i] < kArray[0]) {
                kArray[0] = input[i];
                maxHeap(kArray, 0);
            }
        }
        for (int i = kArray.length - 1; i >= 0; i--) {
            list.add(kArray[i]);
        }
        return list;
    }

    public static void buildHeap(int[] input) {
        for (int i = input.length / 2 - 1; i >= 0; i--) {
            maxHeap(input, i);
        }
    }

    private static void maxHeap(int[] array, int i) {
        int left = 2 * i + 1;
        int right = left + 1;
        int largest = 0;
        if (left < array.length && array[left] > array[i]) largest = left;
        else largest = i;

        if (right < array.length && array[right] > array[largest]) largest = right;

        if (largest != i) {
            int temp = array[i];
            array[i] = array[largest];
            array[largest] = temp;
            maxHeap(array, largest);
        }
    }
}

参考链接:

  1. https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/3chong-jie-fa-miao-sha-topkkuai-pai-dui-er-cha-sou/
  2. https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/jian-zhi-offer-40-zui-xiao-de-k-ge-shu-j-9yze/
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 千羽的编程时光 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 最小的k个数
  • 方法一:快排
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档