ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

533,剑指 Offer-最小的k个数

2021-06-15 07:06:57  阅读:141  来源: 互联网

标签:map arr Offer int res 元素 个数 533 left


Optimists are right. Pessimists are right. It’s up to you to choose which you will be. 

乐观者是对的,悲观者也没错,你自己决定你想成为哪种人。

问题描述

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

 

先排序

这题是让求数组中最小的k个数,很容易想到的一种实现方式就是,先对数组进行排序,然后取前k个即可

1public int[] getLeastNumbers(int[] arr, int k) {
2    //先排序,然后选择前k个即可
3    Arrays.sort(arr);
4    int[] res = new int[k];
5    for (int i = 0; i < k; ++i) {
6        res[i] = arr[i];
7    }
8    return res;
9}

 

使用最大堆

关于堆不熟悉的可以看下《378,数据结构-7,堆》,堆分为最大堆和最小堆,最大堆的堆顶元素是堆中最大的,最小堆的堆顶元素是堆中最小的。这里可以使用最大堆,我们把堆看做是一个容器,最大只能容纳k个元素,具体实现如下:

 

遍历数组中的每一个元素,

1,如果堆的size小于k,直接把遍历的元素加入到堆中。

2,如果堆的size大于等于k,就要判断当前遍历的元素是否比堆顶元素小,

  • 如果比堆顶元素小,就把堆顶元素给移除,把当前遍历的元素加入到堆中。

  • 如果比堆顶元素大,就跳过。

 

原理比较简单,我们来看下代码

 1public int[] getLeastNumbers(int[] arr, int k) {
2    //加个边界条件的判断
3    if (k == 0) {
4        return new int[0];
5    }
6    //创建最大堆
7    PriorityQueue<Integer> queue = new PriorityQueue<>((num1, num2) -> num2 - num1);
8    //先在堆中放数组的前k个元素
9    for (int i = 0; i < k; ++i) {
10        queue.offer(arr[i]);
11    }
12    //因为是最大堆,也就是堆顶的元素是堆中最大的,遍历数组后面元素的时候,
13    //如果当前元素比堆顶元素大,就把堆顶元素给移除,然后再把当前元素放到堆中,
14    for (int i = k; i < arr.length; ++i) {
15        if (queue.peek() > arr[i]) {
16            queue.poll();
17            queue.offer(arr[i]);
18        }
19    }
20    //最后再把堆中元素转化为数组
21    int[] res = new int[k];
22    for (int i = 0; i < k; ++i) {
23        res[i] = queue.poll();
24    }
25    return res;
26}

如果对堆不熟悉,还可以换种方式,使用TreeMap,原理都是一样的。

 1public int[] getLeastNumbers(int[] arr, int k) {
2    //加个边界条件的判断
3    if (k == 0) {
4        return new int[0];
5    }
6    //map中key存放数组中元素,value存放这个元素的个数
7    TreeMap<Integer, Integer> map = new TreeMap<>();
8    int count = 0;
9    for (int i = 0; i < arr.length; i++) {
10        //map中先存放k个元素,之后map中元素始终维持在k个
11        if (count < k) {
12            map.put(arr[i], map.getOrDefault(arr[i], 0) + 1);
13            count++;
14            continue;
15        }
16        Map.Entry<Integer, Integer> entry = map.lastEntry();
17        //从第k+1个元素开始,每次存放的时候都要和map中最大的那个比较,如果比map中最大的小,
18        //就把map中最大的给移除,然后把当前元素加入到map中
19        if (entry.getKey() > arr[i]) {
20            //移除map中最大的元素,如果只有一个直接移除。如果有多个(数组中会有重复的元素),移除一个就行
21            if (entry.getValue() == 1) {
22                map.pollLastEntry();
23            } else {
24                map.put(entry.getKey(), entry.getValue() - 1);
25            }
26            //把当前元素加入到map中
27            map.put(arr[i], map.getOrDefault(arr[i], 0) + 1);
28        }
29    }
30
31    //把map中key存放到集合list中
32    int[] res = new int[k];
33    int index = 0;
34    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
35        int keyCount = entry.getValue();
36        while (keyCount-- > 0) {
37            res[index++] = entry.getKey();
38        }
39    }
40    return res;
41}

 

参考快速排序

快速排序是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。--来自百度百科

 

快速排序说简单一点就是我们找到一个中枢值,这个可以使任何值,我们一般默认是排序范围内的第一个,然后扫描后面的元素,把小于中枢值的往前挪,大于中枢值的往后挪,然后再把中枢值放到合适的位置,这样一轮排序下来,中枢值在他前面的都是比他小的,在他后面的都是比他大的。如果对快排不熟悉的也可以看下《104,排序-快速排序》。我们随便举个例子来看个视频

所以这题的解题思路就是,每次确定中枢值的位置之后,我们要判断这个位置是否等于k(数组的下标是从0开始的)。

 

  • 如果等于k,那么他前面的k个元素都是小于中枢值的,后面的都是大于中枢值的,也就是说他前面的k个元素正是我们要找的。

  • 如果小于k,说明他前面的元素都是我们要找的,但还不够,我们还要继续往后找剩下的。

  • 如果大于k,说明他前面的元素够k个了,我们只需要在他前面找即可,他后面的就不需要了。

 

最后再来看下代码

 1public int[] getLeastNumbers(int[] arr, int k) {
2    int[] res = new int[k];
3    quickSort(arr, res, k, 0, arr.length - 1);
4    return res;
5}
6
7private void quickSort(int[] arr, int[] res, int k, int left, int right) {
8    //快排的实现方式有多种,我们选择了其中的一种
9    int start = left;
10    int end = right;
11    while (left < right) {
12        while (left < right && arr[right] >= arr[start]) {
13            right--;
14        }
15        while (left < right && arr[left] <= arr[start]) {
16            left++;
17        }
18        swap(arr, left, right);
19    }
20    swap(arr, left, start);
21    //注意这里,start是数组中元素的下标。在start之前的元素都是比start指向的元素小,
22    //后面的都是比他大。如果k==start,正好start之前的k个元素是我们要找的,也就是
23    //数组中最小的k个,如果k>start,说明前k个元素不够,我们还要往后再找找。如果
24    //k<start,说明前k个足够了,我们只需要在start之前找k个即可。
25    if (left > k) {
26        quickSort(arr, res, k, start, left - 1);
27    } else if (left < k) {
28        quickSort(arr, res, k, left + 1, end);
29    } else {
30        //取前面的k个即可
31        for (int m = 0; m < k; ++m) {
32            res[m] = arr[m];
33        }
34    }
35}
36
37//交换数组中两个元素的值
38private void swap(int[] arr, int i, int j) {
39    if (i == j)
40        return;
41    int temp = arr[i];
42    arr[i] = arr[j];
43    arr[j] = temp;
44}

 

总结

这题使用快排的思路相对于其他两种实现方式要复杂一点,但效率要比前面两种高很多。

 

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

521,滑动窗口解最大连续1的个数 III

443,滑动窗口最大值

407,动态规划和滑动窗口解决最长重复子数组

398,双指针求无重复字符的最长子串

 

 

截止到目前我已经写了500多道算法题了,为了方便大家阅读,我把部分算法题整理成了pdf文档,目前有900多页,大家可以在公众号中回复关键字“pdf”即可获取下载链接。

 

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=你点的每个赞,我都认真当成了喜欢

标签:map,arr,Offer,int,res,元素,个数,533,left
来源: https://blog.51cto.com/u_4774266/2902947

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有