大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。
题目
剑指 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]
限制:
0 <= k <= arr.length <= 100000 <= arr[i] <= 10000
暴力解法(节省键盘寿命)
思路
- 对整个数组进行从小到大的排序;
- 用数组的
slice方法复制前k个元素。
实现
/**
* @param {number[]} arr
* @param {number} k
* @return {number[]}
*/
var getLeastNumbers = function(arr, k) {
return arr.sort((a, b) => a - b).slice(0, k);
};
结果
一行代码搞定,简直完美~
一看执行用时,好家伙这也太慢了。那我们正儿八经的想一想有没有什么解法是更快的。其实每种排序我们加个限制,排到第k位就结束,起码能打败50%的选手,但是我们如果想追求尽可能找到最优解的话,首选快速排序。为什么呢?首先明确快速排序的特性,就是快嘛。然后我们每一次找基准分成左右两个部分,左边存放比基准值小的,右边存放大的。那么当左边部分的长度小于k时,我们都不用进行排序就找到了前面部分。所以这种方式我们只需要找数量够了即可,效率是最高的。
快速排序Bug版
/**
* @param {number[]} arr
* @param {number} k
* @return {number[]}
*/
var getLeastNumbers = function(arr, k) {
let result = [];
// 长度不够k就进行快排拿
while (result.length < k) {
let [ minArr, maxArr ] = findMinArray(arr);
if ((result.length + minArr.length) <= k) {
result = result.concat(minArr);
arr = maxArr;
} else {
arr = minArr;
}
}
return result;
};
// 找到数值比较小的那部分
function findMinArray(arr) {
// 左边比基准值小, 右边比基准值大
let minArr = [], maxArr = [];
// 佛系取个基准
let base = Math.random() * arr.length | 0;
const n = arr.length;
// 切割成左右两个模块
for (let i = 0; i < n; i++) {
if (arr[i] <= arr[base]) {
minArr.push(arr[i]);
} else {
maxArr.push(arr[i]);
}
}
return [ minArr, maxArr ];
}
本来想着一套乱披风锤法带走,但是很不幸,发现没办法通过所有的测试用例。因为这里想快速把小于某个基准值的放在左边,大于的放在右边。结果我们要找的数组,刚好有重复的值在基准的左右两边,把人整傻眼了。
找到了问题,那么我们来写个if语句对症下药。每轮统计最小值数组的时候,顺带记录一下他们的值是不是全部相同,避免陷入无限循环中去。
最终代码
/**
* @param {number[]} arr
* @param {number} k
* @return {number[]}
*/
var getLeastNumbers = function(arr, k) {
let result = [];
// 长度不够k就进行快排拿
while (result.length < k) {
let [ minArr, maxArr, repeat ] = findMinArray(arr);
if ((result.length + minArr.length) <= k) {
result = result.concat(minArr);
arr = maxArr;
} else if (repeat) {
// 如果全是重复元素的话,切一段补上即可
result = result.concat(minArr.slice(0, k - result.length));
} else {
arr = minArr;
}
}
return result;
};
function findMinArray(arr) {
// 左边比基准值小, 右边比基准值大
let minArr = [], maxArr = [];
// 佛系取个基准
let base = Math.random() * arr.length | 0;
const n = arr.length;
// 数组从0开始,我们取个它永远得不到的值
let prevCount = -1;
// 是否全都一样
let repeat = true;
// 切割成左右两个模块
for (let i = 0; i < n; i++) {
if (arr[i] <= arr[base]) {
minArr.push(arr[i]);
// 记录重复元素
if (prevCount === -1) {
prevCount = arr[i];
} else if (repeat && prevCount !== arr[i]) {
repeat = false;
}
} else {
maxArr.push(arr[i]);
}
}
return [ minArr, maxArr, repeat ];
}
结果
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。