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

[pull] main from itcharge:main #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
pull merged 13 commits into AlgorithmAndLeetCode:main from itcharge:main
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
c0a1d18
Update 02.Array-Selection-Sort.md
itcharge Aug 17, 2022
7a2e11f
Update 03.Array-Insertion-Sort.md
itcharge Aug 17, 2022
0215d09
Update 04.Array-Shell-Sort.md
itcharge Aug 17, 2022
615cf10
Update 06.Array-Quick-Sort.md
itcharge Aug 17, 2022
308148a
Merge branch 'main' of https://github.com/itcharge/LeetCode-Py
itcharge Aug 18, 2022
8ac0095
Update 09.Array-Bucket-Sort.md
itcharge Aug 18, 2022
f08b806
Update 09.Array-Bucket-Sort.md
itcharge Aug 18, 2022
af0b03c
Update 10.Array-Radix-Sort.md
itcharge Aug 18, 2022
c33adf0
Update 07.Array-Heap-Sort.md
itcharge Aug 18, 2022
5d4d642
更新题解列表
itcharge Aug 18, 2022
01a67f9
Update 06.Array-Quick-Sort.md
itcharge Aug 18, 2022
e78071c
Update 10.Array-Radix-Sort.md
itcharge Aug 18, 2022
4f75179
更新代码模板
itcharge Aug 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Contents/00.Introduction/04.Solutions-List.md
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# LeetCode 题解(已完成 730 道)
# LeetCode 题解(已完成 731 道)

| 题号 | 标题 | 题解 | 标签 | 难度 |
| :------ | :------ | :------ | :------ | :------ |
Expand Down Expand Up @@ -49,6 +49,7 @@
| 0049 | [字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0049.%20%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%88%86%E7%BB%84.md) | 字符串、哈希表 | 中等 |
| 0050 | [Pow(x, n)](https://leetcode.cn/problems/powx-n/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0050.%20Pow%28x%2C%20n%29.md) | 数学、二分查找 | 中等 |
| 0051 | [N 皇后](https://leetcode.cn/problems/n-queens/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0051.%20N%20%E7%9A%87%E5%90%8E.md) | 数组、回溯 | 困难 |
| 0052 | [N皇后 II](https://leetcode.cn/problems/n-queens-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0052.%20N%E7%9A%87%E5%90%8E%20II.md) | 回溯 | 困难 |
| 0053 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0053.%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md) | 数组、分治算法、动态规划 | 简单 |
| 0054 | [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0054.%20%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5.md) | 数组 | 中等 |
| 0055 | [跳跃游戏](https://leetcode.cn/problems/jump-game/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0055.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.md) | 贪心算法、数组、动态规划 | 中等 |
Expand Down
2 changes: 1 addition & 1 deletion Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

## 4. 选择排序算法分析

- **时间复杂度**:排序法所进行的元素之间的比较次数与序列的原始状态无关,时间复杂度总是 $O(n^2)$。
- **时间复杂度**:$O(n^2)$。排序法所进行的元素之间的比较次数与序列的原始状态无关,时间复杂度总是 $O(n^2)$。
- 这是因为无论序列中元素的初始排列状态如何,第 `i` 趟排序要找出值最小元素都需要进行 `n − i` 次元素之间的比较。因此,整个排序过程需要进行的元素之间的比较次数都相同,为 $∑^n_{i=2}(i - 1) = \frac{n(n−1)}{2}$ 次。
- **选择排序适用情况**:选择排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,选择排序方法比较适合于参加排序序列的数据量较小的情况。选择排序的主要优点是仅需要原地操作无需占用其他空间就可以完成排序,因此在空间复杂度要求较高时,可以考虑选择排序。

Expand Down
2 changes: 1 addition & 1 deletion Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

- **最佳时间复杂度**:$O(n)$。最好的情况下(初始时序列已经是升序排列),对应的每个 `i` 值只进行一次元素之间的比较,因而总的比较次数最少,为 $∑^n_{i = 2}1 = n − 1,ドル并不需要移动元素(记录),这是最好的情况。
- **最差时间复杂度**:$O(n^2)$。最差的情况下(初始时序列已经是降序排列),对应的每个 `i` 值都要进行 `i - 1` 次元素之间的比较,总的元素之间的比较次数达到最大值,为 $∑^n_{i=2}(i − 1) = \frac{n(n−1)}{2}$。
- **平均时间复杂度**:$O(n^2)$。如果序列的初始情况是随机的,即参加排序的序列中元素可能出现的各种排列的概率相同,则可取上述最小值和最大值的平均值作为插入排序时所进行的元素之间的比较次数,约为 $n^2/4$。由此得知,插入排序算法的时间复杂度 $O(n^2)$。
- **平均时间复杂度**:$O(n^2)$。如果序列的初始情况是随机的,即参加排序的序列中元素可能出现的各种排列的概率相同,则可取上述最小值和最大值的平均值作为插入排序时所进行的元素之间的比较次数,约为 $\frac{n^2}{4}$。由此得知,插入排序算法的时间复杂度 $O(n^2)$。
- **排序稳定性**:插入排序方法是一种 **稳定排序算法**。

## 5. 插入排序代码实现
Expand Down
4 changes: 2 additions & 2 deletions Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **希尔排序(Shell Sort)基本思想**:
>
> 将整个序列切按照一定的间隔取值划分为若干个子序列,每个子序列分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子序列和插入排序。直至最后一轮排序间隔为 `1`,对整个序列进行插入排序。
> 将整个序列切按照一定的间隔取值划分为若干个子序列,每个子序列分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子序列和对子序列进行插入排序。直至最后一轮排序间隔为 `1`,对整个序列进行插入排序。
>

## 2. 希尔排序算法步骤
Expand All @@ -19,7 +19,7 @@
## 4. 希尔排序算法分析

- **时间复杂度**:介于 $O(n \times \log_2 n)$ 与 $O(n^2)$ 之间。
- 希尔排序方法的速度是一系列间隔数 $gap_i$ 的函数,不太容易弄清楚比较次数与 $gap_i$ 之间的依赖关系,并给出完整的数学分析
- 希尔排序方法的速度是一系列间隔数 $gap_i$ 的函数,而比较次数与 $gap_i$ 之间的依赖关系比较复杂,不太容易给出完整的数学分析
- 由于采用 $gap_i = \lfloor gap_{i-1}/2 \rfloor$ 的方法缩小间隔数,对于具有 $n$ 个元素的序列,若 $gap_1 = \lfloor n/2 \rfloor,ドル则经过 $p = \lfloor \log_2 n \rfloor$ 趟排序后就有 $gap_p = 1,ドル因此,希尔排序方法的排序总躺数为 $\lfloor \log_2 n \rfloor$。
- 从算法中也可以看到,最外层的 `while` 循环为 $\log_2 n$ 数量级,中间层 `do-while` 循环为 `n` 数量级。当子序列分得越多时,子序列内的元素就越少,最内层的 `for` 循环的次数也就越少;反之,当所分的子序列个数减少时,子序列内的元素也随之增多,但整个序列也逐步接近有序,而循环次数却不会随之增加。因此,希尔排序算法的时间复杂度在 $O(n \times \log_2 n)$ 与 $O(n^2)$ 之间。

Expand Down
93 changes: 70 additions & 23 deletions Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,61 +1,105 @@
## 1. 快速排序算法思想

> 快速排序(Quick Sort)基本思想:
> **快速排序(Quick Sort)基本思想**:
>
> 通过一趟排序将无序序列分为独立的两个序列,第一个序列的值均比第二个序列的值小。然后递归地排列两个子序列,以达到整个序列有序。

## 2. 快速排序算法步骤

- 从数组中找到一个基准数。
- 然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧,从而把数组拆分为左右两个部分。
- 再对左右两个部分分别重复第二步,直到各个部分只有一个数,则排序结束。
1. 从序列中找到一个基准数 `pivot`(这里以当前序列第 `1` 个元素作为基准数,即 `pivot = arr[low]`)。
2. 使用双指针,将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧:
1. 使用指针 `i`,指向当前需要处理的元素位置,需要保证位置 `i` 之前的元素都小于基准数。初始时,`i` 指向当前序列的第 `2` 个元素位置。
2. 使用指针 `j` 遍历当前序列,如果遇到 `arr[j]` 小于基准数 `pivot`,则将 `arr[j]` 与当前需要处理的元素 `arr[i]` 交换,并将 `i` 向右移动 `1` 位,保证位置 `i` 之前的元素都小于基准数。
3. 最后遍历完,此时位置 `i` 之前的元素都小于基准数,第 `i - 1` 位置上的元素是最后一个小于基准数 `pivot` 的元素,此位置为基准数最终的正确位置。将基准数与该位置上的元素进行交换。此时,基准数左侧都是小于基准数的元素,右侧都是大于等于基准数的元素。
4. 然后将序列拆分为左右两个子序列。
3. 对左右两个子序列分别重复第 `2` 步,直到各个子序列只有 `1` 个元素,则排序结束。

## 3. 快速排序动画演示

![](https://www.runoob.com/wp-content/uploads/2019/03/quickSort.gif)
![](https://qcdn.itcharge.cn/images/20220817105805.gif)

1. 初始序列为:`[6, 2, 3, 5, 1, 4]`。
2. 第 `1` 趟排序:
1. 选择当前序列第 `1` 个元素 `6` 作为基准数。
2. 从左到右遍历序列:
1. 遇到 `2 < 6`,此时 `i` 与 `j` 相同,指针 `i` 向右移动 `1` 位。
2. 遇到 `3 < 6`,此时 `i` 与 `j` 相同,指针 `i` 向右移动 `1` 位。
3. 遇到 `5 < 6`,此时 `i` 与 `j` 相同,指针 `i` 向右移动 `1` 位。
4. 遇到 `1 < 6`,此时 `i` 与 `j` 相同,指针 `i` 向右移动 `1` 位。
5. 遇到 `4 < 6`,此时 `i` 与 `j` 相同,指针 `i` 向右移动 `1` 位,`i` 到达数组末尾。
3. 最终将基准值 `6` 与最后 `1` 位交换位置,则序列变为 `[4, 2, 3, 5, 1, 6]`。
4. 将序列分为左子序列 `[4, 2, 3, 5, 1]` 和右子序列 `[]`。
3. 第 `2` 趟排序:
1. 左子序列 `[4, 2, 3, 5, 1]` 中选择当前序列第 `1` 个元素 `4` 作为基准数。
2. 从到右遍历左子序列:
1. 遇到 `2 < 4`,此时 `i` 与 `j` 相同,指针 `i` 向右移动 `1` 位。
2. 遇到 `3 < 4`,此时 `i` 与 `j` 相同,指针 `i` 向右移动 `1` 位。
3. 遇到 `5 > 4`,不进行操作;
4. 遇到 `1 < 4`,此时 `i` 指向 `5`,`j` 指向 `1`。则将 `5` 与 `1` 进行交换,指针 `i` 向右移动 `1` 位,`i` 到达数组末尾。
3. 最终将基准值 `4` 与 `1` 交换位置,则序列变为 `[1, 2, 3, 4, 5, 6]`。
4. 依次类推,重复选定基准数,并将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。直到各个子序列只有 `1` 个元素,则排序结束。此时序列变为 `[1, 2, 3, 4, 5, 6]`。

## 4. 快速排序算法分析

- 在参加排序的元素初始时已经有序的情况下,快速排序方法花费的时间最长。此时,第 `1` 趟排序经过 `n - 1` 次比较以后,将第 `1` 个元素仍然确定在原来的位置上,并得到 `1` 个长度为 `n - 1` 的子序列;第 `2` 趟排序进过 `n - 2` 次比较以后,将第 `2` 个元素确定在它原来的位置上,又得到 `1` 个长度为 `n - 2` 的子序列;依次类推,最终总的比较次数为 $(n − 1) + (n − 2) + ... + 1 = \frac{n(n − 1)}{2}$。因此时间复杂度为 $O(n^2)$
快速排序算法的时间复杂度主要跟基准数的选择有关。本文中是将当前序列中第 `1` 个元素作为基准值

- 还有一种情况,若每趟排序后,分界元素正好定位在序列的中间,从而把当前参加排序的序列分成大小相等的前后两个子序列,则对长度为 n 的序列进行快速排序所需要的时间为:
在这种选择下,如果参加排序的元素初始时已经有序的情况下,快速排序方法花费的时间最长。也就是会得到最坏时间复杂度。

$\begin{align}T(n) \le & \ n + 2T(n/2) \\ \le & \ 2n + 4T(n/2) \\ \le & \ 3n + 8T(n/8) \\ & ...... \\ \le & \ (log_2 n)n + nT(1) = O(nlog_2 n) \end{align}$
在这种情况下,第 `1` 趟排序经过 `n - 1` 次比较以后,将第 `1` 个元素仍然确定在原来的位置上,并得到 `1` 个长度为 `n - 1` 的子序列。第 `2` 趟排序进过 `n - 2` 次比较以后,将第 `2` 个元素确定在它原来的位置上,又得到 `1` 个长度为 `n - 2` 的子序列。

因此,快速排序方法的时间复杂度为 $O(nlog_2 n),ドル时间性能显然优于前面讨论的几种排序算法
最终总的比较次数为 $(n − 1) + (n − 2) + ... + 1 = \frac{n(n − 1)}{2}$。因此这种情况下的时间复杂度为 $O(n^2),ドル也是最坏时间复杂度

- 无论快速排序算法递归与否,排序过程中都需要用到堆栈或其他结构的辅助空间来存放当前待排序序列的首、尾位置。最坏的情况下,空间复杂度为 $O(n)$
我们可以改进一下基准数的选择。如果每次我们选中的基准数恰好能将当前序列平分为两份,也就是刚好取到当前序列的中位数

- 若对算法进行一些改写,在一趟排序之后比较被划分所得到的两个子序列的长度,并且首先对长度较短的子序列进行快速排序,这时候需要的空间复杂度可以达到 $O(log_2 n)$
在这种选择下,每一次都将序列从 $n$ 个元素变为 $\frac{n}{2}$ 个元素。此时的时间复杂度公式为 $T(n) = 2 \times T(\frac{n}{2}) + \Theta(n)$。根据主定理可以得出 $T(n) = O(n \times \log_2n),ドル也是最佳时间复杂度

- 快速排序时一种 **不稳定排序算法**,也是一种不适合在链表结构上实现的排序算法。
而在平均情况下,我们可以从当前序列中随机选择一个元素作为基准数。这样,每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 $O(n \times \log_2n),ドル也就是平均时间复杂度。

下面来总结一下:

- **最佳时间复杂度**:$O(n \times \log_2n)$。每一次选择的基准数都是当前序列的中位数,此时算法时间复杂度满足的递推式为 $T(n) = 2 \times T(\frac{n}{2}) + \Theta(n),ドル由主定理可得 $T(n) = O(n \times \log_2n)$。
- **最坏时间复杂度**:$O(n^2)$。每一次选择的基准数都是序列的最终位置上的值,此时算法时间复杂度满足的递推式为 $T(n) = T(n - 1) + \Theta(n),ドル累加可得 $T(n) = O(n^2)$。
- **平均时间复杂度**:$O(n \times \log_2n)$。在平均情况下,每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 $O(n \times \log_2n)$。
- **空间复杂度**:$O(n)$。无论快速排序算法递归与否,排序过程中都需要用到堆栈或其他结构的辅助空间来存放当前待排序序列的首、尾位置。最坏的情况下,空间复杂度为 $O(n)$。如果对算法进行一些改写,在一趟排序之后比较被划分所得到的两个子序列的长度,并且首先对长度较短的子序列进行快速排序,这时候需要的空间复杂度可以达到 $O(log_2 n)$。
- **排序稳定性**:快速排序是一种 **不稳定排序算法**。

## 5. 快速排序代码实现

```Python
import random


class Solution:
# 从 arr[low: high + 1] 中随机挑选一个基准数,并进行移动排序
def randomPartition(self, arr: [int], low: int, high: int):
# 随机挑选一个基准数
i = random.randint(low, high)
arr[i], arr[high] = arr[high], arr[i]
# 将基准数与最低位互换
arr[i], arr[low] = arr[low], arr[i]
# 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上
return self.partition(arr, low, high)


# 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上
def partition(self, arr: [int], low: int, high: int):
i = low - 1
pivot = arr[high]

for j in range(low, high):
if arr[j] <= pivot:
i += 1
pivot = arr[low] # 以第 1 为为基准数
i = low + 1 # 从基准数后 1 位开始遍历,保证位置 i 之前的元素都小于基准数

for j in range(i, high + 1):
# 发现一个小于基准数的元素
if arr[j] < pivot:
# 将小于基准数的元素 arr[j] 与当前 arr[i] 进行换位,保证位置 i 之前的元素都小于基准数
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
# i 之前的元素都小于基准数,所以 i 向右移动一位
i += 1
# 将基准节点放到正确位置上
arr[i - 1], arr[low] = arr[low], arr[i - 1]
# 返回基准数位置
return i - 1

def quickSort(self, arr, low, high):
if low < high:
# 按照基准数的位置,将序列划分为左右两个子序列
pi = self.randomPartition(arr, low, high)
# 对左右两个子序列分别进行递归快速排序
self.quickSort(arr, low, pi - 1)
self.quickSort(arr, pi + 1, high)

Expand All @@ -65,3 +109,6 @@ class Solution:
return self.quickSort(nums, 0, len(nums) - 1)
```

## 参考资料

- 【文章】[快速排序 - OI Wiki](https://oi-wiki.org/basic/quick-sort/)
Loading

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