1. 题目描述

输入 n 个整数,找出其中最小的 k 个数。例如输入 4、5、1、6、2、7、3、8 这 8 个数字,则最小的 4 个数字是 1、2、3、4。

2. 思路分析

利用“快速排序”的中的 partition 操作:返回 index,小于 index 对应元素的元素都放在了左边,大于 index 对应元素的元素都放在右边。

利用这个特性,只要我们的 partition 返回值是 k - 1,那么数组中前 k 个元素已经被摆放到了正确位置,直接遍历输出即可。

由于不需要排序全部,整体的时间复杂度是 O(N)。但美中不足的是:要在原数组操作,除非用 O(N)的空间来做拷贝。除此之外,针对海量动态增加的数据,也不能很好处理。这种情况需要用到“最大堆”,请前往《堆》章节查看。

3. 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function partiton(arr = [], start, end) {
const length = arr.length
if (!length) {
return null
}

let v = arr[start],
left = start + 1,
right = end

while (1) {
while (left <= end && arr[left] <= v) ++left
while (right >= start + 1 && arr[right] >= v) --right

if (left >= right) {
break
}

;[arr[left], arr[right]] = [arr[right], arr[left]]
++left
--right
}

;[arr[right], arr[start]] = [arr[start], arr[right]]
return right
}

function getKthNumbers(nums = [], k) {
if (k <= 0) {
return null
}

const length = nums.length
const result = new Array(k)
let start = 0,
end = length - 1
let index = partiton(nums, start, end)
while (index !== k - 1) {
if (index > k - 1) {
// 前k个元素在 [start, index] 下标范围内
// 要进一步处理,缩小区间
end = index - 1
index = partiton(nums, start, end)
} else {
// [start, index]都属于小于k的元素,但不是全部
// 剩下要处理的区间是 [index + 1, end]
start = index + 1
index = partiton(nums, start, end)
}
}

for (let i = 0; i < k; ++i) {
result[i] = nums[i]
}

return result
}

/**
* 以下是测试代码
*/

console.log(getKthNumbers([4, 5, 1, 6, 2, 7, 3, 8], 4)) // output: [2, 3, 1, 4]
console.log(getKthNumbers([10, 2], 1))