5
5
6
6
## 题目大意
7
7
8
- 给定整数数组 ` arr ` ,再给定一个整数 ` k ` 。
8
+ ** 描述 ** : 给定整数数组 ` arr ` ,再给定一个整数 ` k ` 。
9
9
10
- 要求:返回数组 ` arr ` 中最小的 ` k ` 个数。
10
+ ** 要求** :返回数组 ` arr ` 中最小的 ` k ` 个数。
11
+
12
+ ** 说明** :
13
+
14
+ - 0ドル \le k \le arr.length \le 10000$。
15
+ - 0ドル \le arr[ i] \le 10000$。
16
+
17
+ ** 示例** :
18
+
19
+ ``` Python
20
+ 输入:arr = [3 ,2 ,1 ], k = 2
21
+ 输出:[1 ,2 ] 或者 [2 ,1 ]
22
+
23
+
24
+ 输入:arr = [0 ,1 ,2 ,1 ], k = 1
25
+ 输出:[0 ]
26
+ ```
11
27
12
28
## 解题思路
13
29
14
30
直接可以想到的思路是:排序后输出数组上对应的最小的 k 个数。所以问题关键在于排序方法的复杂度。
15
31
16
32
冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,解答会超时。
17
33
18
- 可考虑堆排序、归并排序、快速排序。本题使用堆排序。具体做法如下:
34
+ 可考虑堆排序、归并排序、快速排序。
19
35
20
- 1 . 利用数组前 ` k ` 个元素,建立大小为 ` k ` 的大顶堆。
21
- 2 . 遍历数组 ` [k, size - 1] ` 的元素,判断其与堆顶元素关系,如果比堆顶元素小,则将其赋值给堆顶元素,再对大顶堆进行调整。
22
- 3 . 最后输出前调整过后的大顶堆的前 ` k ` 个元素。
36
+ ### 思路 1:堆排序(基于大顶堆)
23
37
24
- ## 代码
38
+ 具体做法如下:
39
+
40
+ 1 . 使用数组前 ` k ` 个元素,维护一个大小为 ` k ` 的大顶堆。
41
+ 2 . 遍历数组 ` [k, size - 1] ` 的元素,判断其与堆顶元素关系,如果遇到比堆顶元素小的元素,则将与堆顶元素进行交换。再将这 ` k ` 个元素调整为大顶堆。
42
+ 3 . 最后输出大顶堆的 ` k ` 个元素。
43
+
44
+ ### 思路 1:代码
25
45
26
46
``` Python
27
47
class Solution :
@@ -59,6 +79,7 @@ class Solution:
59
79
return arr
60
80
61
81
self .buildMaxHeap(arr, k)
82
+
62
83
for i in range (k, size):
63
84
if arr[i] < arr[0 ]:
64
85
arr[i], arr[0 ] = arr[0 ], arr[i]
@@ -67,3 +88,74 @@ class Solution:
67
88
return arr[:k]
68
89
```
69
90
91
+ ### 思路 1:复杂度分析
92
+
93
+ - ** 时间复杂度** :$O(n\log_2k)$。
94
+ - ** 空间复杂度** :$O(1)$。
95
+
96
+ ### 思路 2:快速排序
97
+
98
+ 使用快速排序在每次调整时,都会确定一个元素的最终位置,且以该元素为界限,将数组分成了左右两个子数组,左子数组中的元素都比该元素小,右子树组中的元素都比该元素大。
99
+
100
+ 这样,只要某次划分的元素恰好是第 ` k ` 个元素下标,就找到了数组中最小的 ` k ` 个数所对应的区间,即 ` [0, k - 1] ` 。 并且我们只需关注第 ` k ` 个最小元素所在区间的排序情况,与第 ` k ` 个最小元素无关的区间排序都可以忽略。这样进一步减少了执行步骤。
101
+
102
+ ### 思路 2:代码
103
+
104
+ ``` Python
105
+ import random
106
+
107
+ class Solution :
108
+ # 从 arr[low: high + 1] 中随机挑选一个基准数,并进行移动排序
109
+ def randomPartition (self , arr : [int ], low : int , high : int ):
110
+ # 随机挑选一个基准数
111
+ i = random.randint(low, high)
112
+ # 将基准数与最低位互换
113
+ arr[i], arr[low] = arr[low], arr[i]
114
+ # 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上
115
+ return self .partition(arr, low, high)
116
+
117
+ # 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上
118
+ def partition (self , arr : [int ], low : int , high : int ):
119
+ pivot = arr[low] # 以第 1 为为基准数
120
+ i = low + 1 # 从基准数后 1 位开始遍历,保证位置 i 之前的元素都小于基准数
121
+
122
+ for j in range (i, high + 1 ):
123
+ # 发现一个小于基准数的元素
124
+ if arr[j] < pivot:
125
+ # 将小于基准数的元素 arr[j] 与当前 arr[i] 进行换位,保证位置 i 之前的元素都小于基准数
126
+ arr[i], arr[j] = arr[j], arr[i]
127
+ # i 之前的元素都小于基准数,所以 i 向右移动一位
128
+ i += 1
129
+ # 将基准节点放到正确位置上
130
+ arr[i - 1 ], arr[low] = arr[low], arr[i - 1 ]
131
+ # 返回基准数位置
132
+ return i - 1
133
+
134
+ def quickSort (self , arr , low , high , k ):
135
+ size = len (arr)
136
+ if low < high:
137
+ # 按照基准数的位置,将序列划分为左右两个子序列
138
+ pi = self .randomPartition(arr, low, high)
139
+ if pi == k:
140
+ return arr[:k]
141
+ if pi > k:
142
+ # 对左子序列进行递归快速排序
143
+ self .quickSort(arr, low, pi - 1 , k)
144
+ if pi < k:
145
+ # 对右子序列进行递归快速排序
146
+ self .quickSort(arr, pi + 1 , high, k)
147
+
148
+ return arr[:k]
149
+
150
+ def getLeastNumbers (self , arr : List[int ], k : int ) -> List[int ]:
151
+ size = len (arr)
152
+ if k >= size:
153
+ return arr
154
+ return self .quickSort(arr, 0 , size - 1 , k)
155
+ ```
156
+
157
+ ### 思路 2:复杂度分析
158
+
159
+ - ** 时间复杂度** :$O(n)$。证明过程可参考「算法导论 9.2:期望为线性的选择算法」。
160
+ - ** 空间复杂度** :$O(\log_2 n)$。递归使用栈空间的空间代价期望为 $O(\log_2n)$。
161
+
0 commit comments