剑指 Offer 40. 最小的k个数

86 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

剑指offer-32-最小的 K 个数

Leetcode : leetcode-cn.com/problems/zu…

GitHub : github.com/nateshao/le…

剑指 Offer 40. 最小的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]

Go傻瓜式

func getLeastNumbers(arr []int, k int) []int {  
     sort.Ints(arr)
    return arr[:k]
}

image.png

快排Go

func getLeastNumbers(arr []int, k int) []int {
       res:=quickSort(arr,0,len(arr)-1)
       return res[:k]
}

func quickSort(arr []int,left,right int) []int{ //快排
    if left>right{
        return nil
    }
    i,j,cur:=left,right,arr[left]
    for i<j{
        for i<j && arr[j]>=cur{  //尾指针先走
            j--
        }
        for i<j && arr[i]<=cur{
            i++
        }
        arr[i],arr[j]=arr[j],arr[i]  //交换
    }
    arr[i],arr[left]=arr[left],arr[i]  //交换选定的分界元素和左右分分界的arr[i]元素
    quickSort(arr,left,i-1)  //递归左边
    quickSort(arr,i+1,right) //递归右边
    return arr
}

方法一:快排

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

快速排序原理:

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

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

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

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

复杂度分析:

  • 时间复杂度O(N logN): 库函数、快排等排序算法的平均时间复杂度为O(N log N)。
  • 空间复杂度O(N) : 快速排序的递归深度最好(平均)为O(logN),最差情况(即输入数组完全倒 序)为O(N)。
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. leetcode-cn.com/problems/zu…
  2. leetcode-cn.com/problems/zu…