Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 39c8c8e

Browse files
committed
Update 剑指 Offer 40. 最小的k个数.md
1 parent 9771308 commit 39c8c8e

File tree

1 file changed

+99
-7
lines changed

1 file changed

+99
-7
lines changed

‎Solutions/剑指 Offer 40. 最小的k个数.md‎

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,43 @@
55

66
## 题目大意
77

8-
给定整数数组 `arr`,再给定一个整数 `k`
8+
**描述**:给定整数数组 `arr`,再给定一个整数 `k`
99

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+
```
1127

1228
## 解题思路
1329

1430
直接可以想到的思路是:排序后输出数组上对应的最小的 k 个数。所以问题关键在于排序方法的复杂度。
1531

1632
冒泡排序、选择排序、插入排序时间复杂度 $O(n^2)$ 太高了,解答会超时。
1733

18-
可考虑堆排序、归并排序、快速排序。本题使用堆排序。具体做法如下:
34+
可考虑堆排序、归并排序、快速排序。
1935

20-
1. 利用数组前 `k` 个元素,建立大小为 `k` 的大顶堆。
21-
2. 遍历数组 `[k, size - 1]` 的元素,判断其与堆顶元素关系,如果比堆顶元素小,则将其赋值给堆顶元素,再对大顶堆进行调整。
22-
3. 最后输出前调整过后的大顶堆的前 `k` 个元素。
36+
### 思路 1:堆排序(基于大顶堆)
2337

24-
## 代码
38+
具体做法如下:
39+
40+
1. 使用数组前 `k` 个元素,维护一个大小为 `k` 的大顶堆。
41+
2. 遍历数组 `[k, size - 1]` 的元素,判断其与堆顶元素关系,如果遇到比堆顶元素小的元素,则将与堆顶元素进行交换。再将这 `k` 个元素调整为大顶堆。
42+
3. 最后输出大顶堆的 `k` 个元素。
43+
44+
### 思路 1:代码
2545

2646
```Python
2747
class Solution:
@@ -59,6 +79,7 @@ class Solution:
5979
return arr
6080

6181
self.buildMaxHeap(arr, k)
82+
6283
for i in range(k, size):
6384
if arr[i] < arr[0]:
6485
arr[i], arr[0] = arr[0], arr[i]
@@ -67,3 +88,74 @@ class Solution:
6788
return arr[:k]
6889
```
6990

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

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /