剑指offer2——最小的K个数

原题链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/

原题

输入整数数组 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法

1. 直接sort排序取前k

最直接最暴力的方式,不做过多叙述

2. 小顶堆

建立一个小顶堆,将所有的数据放进去,然后再取出k个数字即可。
在Java中使用java.util.PriorityQueue类,其默认是一个小顶堆。
代码如下:

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int i = 0; i < arr.length; i ++) {
            queue.add(arr[i]);
        }
        int[] res = new int[k];
        for (int i = 0; i < k; i ++) {
            res[i] = queue.poll();
        }
        return res;
    }
}

3. 大顶堆

使用大顶堆维护一个容量只有k + 1个的堆,循环遍历数组:
(1) 若当前堆中元素数量小于k,则直接将当前元素添加进入堆
(2) 若当前堆中元素数量等于k个,则将当前元素添加进入堆,并取出堆顶元素
这样保证堆中的所有元素为最小
代码如下:

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>(k + 1, new Comparator<>() {
            @Override
             public int compare(Integer o1, Integer o2) {
                return o2 - o1;
             };
        });
        for (int i = 0; i < arr.length; i ++) {
            queue.add(arr[i]);
            if (queue.size() == k + 1) {
                queue.poll();
            }
        }
        int[] res = new int[k];
        for (int i = 0; i < k; i ++) {
            res[i] = queue.poll();
        }
        return res;
    }
}

4. 类似于快排的思想

快排大致思想

在快速排序的过程中,主要是将带排序数组的第一个数字作为一个临界值,然后通过左右双指针遍历待排序数组,并进行交换,使得所有小于临界值的数字在临界值的左边,所有大于临界值的数字在临界值的右边

所以若临界值所在的下标为index,那么其右边的index个数字一定是该数组中最小的index个数字。

快速排序核心代码(伪代码版本)

public void fastSort(int arr[], int start, int end) {
    if (start >= end) return;
    int num = arr[start];
    int l = start, r = end;
    while (l < r) {
        while (l < r && arr[r] >= num) {
            r --;
        }
        // 代表交换两个值
        swap(arr[l], arr[r]);
        while (l < r && arr[l]  <= num) {
            l ++;
        }
        swap(arr[l], arr[r]);
    }
    // 这里的mid就是临界值的下标
    int mid = l;
    fastSort(arr, start, mid);
    fastSort(arr, mid + 1, end);
}

因此在获取到临界值排序后的下标之后,就有以下三种情况
**设临界值左边(包含临界值)的长度length = mid - start + 1 **

  1. length == k
    喜大普奔,直接返回即可,左边刚好够
  2. length < k
    左边的不够,那么需要再从右边拿length - k个数字才满足最小的k个数字的要求,因此对右边[mid+1, end]这个区间再一次执行排序,只不过此时其对应的k变成了k - length
  3. length > k
    左边的的比k个多,那么从中取k个就可以?

这里一定要注意,左边的length个数字并不保证是有序的,其只能保证其一定小于临界值,因此不能直接从左边区域拿前k个数字作为答案

因此最终的代码大概如下:

import java.util.*;
class Main {
    static public void main(String[] argv) {
        int[] input = {0,1,1,1,4,5,3,7,7,8,10,2,7,8,0,5,2,16,12,1,19,15,5,18,2,2,22,15,8,22,17,6,22,6,22,26,32,8,10,11,2,26,9,12,9,7,28,33,20,7,2,17,44,3,52,27,2,23,19,56,56,58,36,31,1,19,19,6,65,49,27,63,29,1,69,47,56,61,40,43,10,71,60,66,42,44,10,12,83,69,73,2,65,93,92,47,35,39,13,75};
        Solution sol = new Solution();
        int[] res = sol.getLeastNumbers(input, 75);
        for (int i = 0; i < res.length; i ++) {
            System.out.printf("%d ", res[i]);
        }
        System.out.println();
    }
}
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (arr.length == 0 || k == 0) return new int[0];
        fastSort(arr, 0, arr.length - 1, k);
        int[] res = new int[k];
        for (int i = 0; i < k; i ++) {
            res[i] = arr[i];
        }
        Arrays.sort(res);
        return res;
    }

    private void fastSort(int[] arr, int l, int r, int k) {
        if (l >= arr.length || l > r) return;
        int firstNum = arr[l];
        int lastR = r;
        int lastL = l;
        while (l < r) {
            while (l < r && arr[r] >= firstNum) {
                r --;
            }
//            System.out.printf("sl: %d sr: %d l: %d r: %d k: %d\n", lastL , lastR, l, r, k);
            {
                int temp = arr[r];
                arr[r] = arr[l];
                arr[l] = temp;
            }
            while (l < r && arr[l] <= firstNum) {
                l ++;
            } 
//            System.out.printf("sl: %d sr: %d l: %d r: %d k: %d\n", lastL , lastR, l, r, k);
            {
                int temp = arr[r];
                arr[r] = arr[l];
                arr[l] = temp;
            }
        }
        int mid = l;
        int length = mid - lastL + 1;
        if (k > length) {
            fastSort(arr, mid + 1, lastR, k - length);
        }
        else if (k < length) {
            fastSort(arr, lastL, mid, k);
        }
    }

}

后记

三种解法提交的情况

从上往下依次是大顶堆、小顶堆、类似于快排的思想

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值