From c0a1d185fe3b1a1509337c6cc6cd5360ea51a0be Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月17日 17:11:27 +0800 Subject: [PATCH 01/12] Update 02.Array-Selection-Sort.md --- Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md b/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md index 570d756e..2a56e3bd 100644 --- a/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md +++ b/Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md @@ -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}$ 次。 - **选择排序适用情况**:选择排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,选择排序方法比较适合于参加排序序列的数据量较小的情况。选择排序的主要优点是仅需要原地操作无需占用其他空间就可以完成排序,因此在空间复杂度要求较高时,可以考虑选择排序。 From 7a2e11fa6338e66af0325895a630458b073641ed Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月17日 17:11:32 +0800 Subject: [PATCH 02/12] Update 03.Array-Insertion-Sort.md --- Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md b/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md index fadff237..97fb9491 100644 --- a/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md +++ b/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md @@ -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. 插入排序代码实现 From 0215d090bb7c57ceccdf10dfc7274851c0fc23a6 Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月17日 17:11:38 +0800 Subject: [PATCH 03/12] Update 04.Array-Shell-Sort.md --- Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md b/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md index 0493e4be..2d38e00d 100644 --- a/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md +++ b/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md @@ -2,7 +2,7 @@ > **希尔排序(Shell Sort)基本思想**: > -> 将整个序列切按照一定的间隔取值划分为若干个子序列,每个子序列分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子序列和插入排序。直至最后一轮排序间隔为 `1`,对整个序列进行插入排序。 +> 将整个序列切按照一定的间隔取值划分为若干个子序列,每个子序列分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子序列和对子序列进行插入排序。直至最后一轮排序间隔为 `1`,对整个序列进行插入排序。 > ## 2. 希尔排序算法步骤 @@ -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)$ 之间。 From 615cf10259112c617bc026791cc20b49e40de6cd Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月17日 17:11:49 +0800 Subject: [PATCH 04/12] Update 06.Array-Quick-Sort.md --- .../02.Array-Sort/06.Array-Quick-Sort.md | 91 ++++++++++++++----- 1 file changed, 69 insertions(+), 22 deletions(-) diff --git a/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md b/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md index c26091cb..f8654a21 100644 --- a/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md +++ b/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md @@ -6,56 +6,100 @@ ## 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) @@ -65,3 +109,6 @@ class Solution: return self.quickSort(nums, 0, len(nums) - 1) ``` +## 参考资料 + +- 【文章】[快速排序 - OI Wiki](https://oi-wiki.org/basic/quick-sort/) From 8ac00955c3d5bfd89987feab834edf036ff93542 Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月18日 15:08:58 +0800 Subject: [PATCH 05/12] Update 09.Array-Bucket-Sort.md --- Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md b/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md index 69d6d200..5ccf2f18 100644 --- a/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md +++ b/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md @@ -17,7 +17,7 @@ ## 3. 计数排序动画演示 -![](https://www.runoob.com/wp-content/uploads/2019/03/countingSort.gif) +![计数排序](http://qcdn.itcharge.cn/images/20220818140454.gif) ## 4. 计数排序算法分析 @@ -59,3 +59,4 @@ class Solution: return self.countingSort(nums) ``` + From f08b80625fa7bfdc80574cccc88b07ee99f9b4eb Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月18日 15:09:06 +0800 Subject: [PATCH 06/12] Update 09.Array-Bucket-Sort.md --- Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md b/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md index 97af6393..3d1ef540 100644 --- a/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md +++ b/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md @@ -8,7 +8,7 @@ 1. 根据原始数组的值域范围,将数组划分为 `k` 个相同大小的子区间,每个区间称为一个桶。 2. 遍历原始数组元素,将每个元素装入对应区间的桶中。 -3. 对每个桶内的元素单独排序(使用插入、归并、快排等算法)。 +3. 对每个桶内的元素单独排序(使用插入排序、归并排序、快排排序等算法)。 4. 最后按照区间顺序将桶内的元素合并起来,完成排序。 ## 3. 桶排序图解演示 From af0b03c273d6e11be15048007d9b761f8d7db53a Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月18日 15:09:10 +0800 Subject: [PATCH 07/12] Update 10.Array-Radix-Sort.md --- .../01.Array/02.Array-Sort/10.Array-Radix-Sort.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md b/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md index e82fadd1..f4ff1781 100644 --- a/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md +++ b/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md @@ -11,13 +11,20 @@ 下面我们以最低位优先法为例,讲解一下算法步骤。 1. 遍历数组元素,获取数组最大值元素,并取得位数。 -2. 以个位元素为索引,对数组元素排序。 -3. 合并数组。 -4. 之后依次以十位,百位,...,最大值元素的最高位 为索引,进行排序,并合并数组,最终完成排序。 +2. 定义一个长度为 `10` 的桶 `buckets`,分别代表 `0 ~ 9` 这 `10` 位数字。 +3. 以个位元素为索引,根据数组元素个位上的值,将数组元素存入对应数字的桶中。 +4. 清空原始数组,并从桶中依次取出对应元素,重新加入到原始数组中。 +5. 之后分别以十位,百位,...,最大值元素的最高位为索引,根据元素对应位上的数字,存入对应数字的桶中。并合并数组,完成排序。 ## 3. 基数排序动画演示 -![](https://www.runoob.com/wp-content/uploads/2019/03/radixSort.gif) +![](https://qcdn.itcharge.cn/images/20220818144208.gif) + +1. 初始序列为 `[32, 1, 10, 96, 57, 7, 62, 47, 82, 25, 79, 5]`,序列所有元素的最大位数为 `2`。 +2. 以个位为索引,根据元素个位上的数字,将其分别存入到 `0` ~ `9` 这 `10` 个桶中。 +3. 清空原始数组,并从桶中依次取出对应元素,重新加入到原始数组中。此时序列变为 `[10, 1, 32, 62, 82, 25, 5, 96, 57, 7, 47, 79]`。 +4. 以十位为索引,根据元素十位上的数字,将其分别存入到 `0` ~ `9` 这 `10` 个桶中。 +5. 清空原始数组,并从桶中依次取出对应元素,重新加入到原始数组中。此时序列变为 `[1, 5, 7, 10, 25, 32, 47, 57, 62, 79, 82, 96]`,完成排序。 ## 4. 基数排序算法分析 From c33adf059b92e931e089b93f64f2322de4b2b69a Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月18日 15:09:45 +0800 Subject: [PATCH 08/12] Update 07.Array-Heap-Sort.md --- .../02.Array-Sort/07.Array-Heap-Sort.md | 98 ++++++++++++------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md b/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md index 2f8057bb..f7f6843b 100644 --- a/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md +++ b/Contents/01.Array/02.Array-Sort/07.Array-Heap-Sort.md @@ -1,71 +1,93 @@ ## 1. 堆排序算法思想 -> 堆排序(Heap sort)基本思想: +> **堆排序(Heap sort)基本思想**: > -> 借用「堆结构」所设计的排序算法。将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆维持大顶堆性质。 +> 借用「堆结构」所设计的排序算法。将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆结构继续维持大顶堆性质。 ### 1.1 堆的定义 -堆:符合以下两个条件之一的完全二叉树: +**堆(Heap)**:符合以下两个条件之一的完全二叉树: -- 大顶堆:根节点值 ≥ 子节点值。 -- 小顶堆:根节点值 ≤ 子节点值。 +- **大顶堆**:根节点值 ≥ 子节点值。 +- **小顶堆**:根节点值 ≤ 子节点值。 -## 2. 堆排序算法步骤 +### 1.2 堆排序算法步骤 -- 首先将无序序列构造成第 `1` 个大顶堆(初始堆),使得 `n` 个元素的最大值处于序列的第 `1` 个位置。 -- 然后交换序列的第 `1` 个元素(最大值元素)与最后一个元素的位置。 -- 此后,再把序列的前 `n - 1` 个元素组成的子序列调整成一个新的大顶堆,这样又得到第 `2` 个最大值元素,把子序列的第 `1` 个元素(最大值元素)与第 `n - 1` 个元素交换位置。 -- 此后再把序列的前 `n - 2` 个元素调整成一个新的大顶堆,......,如此下去,直到整个序列变换成一个有序序列。 +1. **建立初始堆**:将无序序列构造成第 `1` 个大顶堆(初始堆),使得 `n` 个元素的最大值处于序列的第 `1` 个位置。 +2. **调整堆**:交换序列的第 `1` 个元素(最大值元素)与第 `n` 个元素的位置。将序列前 `n - 1` 个元素组成的子序列调整成一个新的大顶堆,使得 `n - 1` 个元素的最大值处于序列第 `1` 个位置,从而得到第 `2` 个最大值元素。 +3. **调整堆**:交换子序列的第 `1` 个元素(最大值元素)与第 `n - 1` 个元素的位置。将序列前 `n - 2` 个元素组成的子序列调整成一个新的大顶堆,使得 `n - 2` 个元素的最大值处于序列第 `1` 个位置,从而得到第 `3` 个最大值元素。 +4. 依次类推,不断交换子序列的第 `1` 个元素(最大值元素)与当前子序列最后一个元素位置,并将其调整成新的大顶堆。直到子序列剩下一个元素时,排序结束。此时整个序列就变成了一个有序序列。 -可见堆排序算法主要涉及「初始堆建立方法」和「堆调整方法」。 +从堆排序算法步骤中可以看出:堆排序算法主要涉及「调整堆」和「建立初始堆」两个步骤。 -### 2.1 堆调整方法 +## 2. 调整堆方法 -堆调整方法就是:把移走了最大值元素以后的剩余元素组成的序列再构造为一个新的堆积。具体步骤如下: +### 2.1 调整堆方法介绍 -- 从根节点开始,自上而下地调整节点的位置,使其成为堆积。即把序号为 `i` 的节点与其左子树节点(序号为 `2 * i`)、右子树节点(序号为 `2 * i + 1`)中值最大的节点交换位置。 -- 因为交换了位置,使得当前节点的左右子树原有的堆积特性被破坏。于是,从当前节点的左右子树节点开始,自上而下继续进行类似的调整。 -- 如此下去直到整棵完全二叉树成为一个大顶堆。 +**调整堆方法**:把移走了最大值元素以后的剩余元素组成的序列再构造为一个新的堆积。具体步骤如下: -### 2.2 初始堆建立方法 +1. 从根节点开始,自上而下地调整节点的位置,使其成为堆积。 + 1. 判断序号为 `i` 的节点与其左子树节点(序号为 `2 * i`)、右子树节点(序号为 `2 * i + 1`)中值关系。 + 2. 如果序号为 `i` 节点大于等于左右子节点值,则排序结束。 + 3. 如果序号为 `i` 节点小于左右子节点值,则将序号为 `i` 节点与左右子节点中值最大的节点交换位置。 +2. 因为交换了位置,使得当前节点的左右子树原有的堆积特性被破坏。于是,从当前节点的左右子树节点开始,自上而下继续进行类似的调整。 +3. 依次类推,直到整棵完全二叉树成为一个大顶堆。 -- 如果原始序列对应的完全二叉树(不一定是堆)的深度为 `d`,则从 `d - 1` 层最右侧分支节点(序号为 $\lfloor n/2 \rfloor$)开始,初始时令 $i = \lfloor n/2 \rfloor,ドル调用堆调整算法。 -- 每调用一次堆调整算法,执行一次 `i = i - 1`,直到 `i == 1` 时,再调用一次,就把原始序列调整为了一个初始堆。 +### 2.2 调整堆方法演示 -## 3. 堆排序动画演示 +![](http://qcdn.itcharge.cn/images/20211019172530.gif) -### 3.1 堆调整方法演示 +1. 交换序列的第 `1` 个元素 `90` 与最后 `1` 个元素 `19` 的位置,此时当前节点为根节点 `19`。 +2. 判断根节点 `19`与其左右子节点值,因为 `17 < 19 < 36`,所以将根节点 `19` 与左子节点 `36` 互换位置,此时当前节点为根节点 `19`。 +3. 判断当前节点 `36` 与其左右子节点值,因为 `19 < 25 < 26`,所以将当前节点 `19` 与右节点 `26` 互换位置。调整堆结束。 -![](http://qcdn.itcharge.cn/images/20211019172530.gif) +## 3. 建立初始堆方法 + +### 3.1 建立初始堆方法介绍 -### 3.2 初始堆建立方法演示 +1. 如果原始序列对应的完全二叉树(不一定是堆)的深度为 `d`,则从 `d - 1` 层最右侧分支节点(序号为 $\lfloor \frac{n}{2} \rfloor$)开始,初始时令 $i = \lfloor \frac{n}{2} \rfloor,ドル调用调整堆算法。 +2. 每调用一次调整堆算法,执行一次 `i = i - 1`,直到 `i == 1` 时,再调用一次,就把原始序列调整为了一个初始堆。 -![](http://qcdn.itcharge.cn/images/20211019172539.gif) +### 3.2 建立初始堆方法演示 -### 3.3 堆排序方法演示 +![](https://qcdn.itcharge.cn/images/20220818111455.gif) + +1. 原始序列为 `[2, 7, 26, 25, 19, 17, 1, 90, 3, 36]`,对应完全二叉树的深度为 `3`。 +2. 从第 `2` 层最右侧的分支节点,也就序号为 `5` 的节点开始,调用堆调整算法,使其与子树形成大顶堆。 +3. 节点序号减 `1`,对序号为 `4` 的节点,调用堆调整算法,使其与子树形成大顶堆。 +4. 节点序号减 `1`,对序号为 `3` 的节点,调用堆调整算法,使其与子树形成大顶堆。 +5. 节点序号减 `1`,对序号为 `2` 的节点,调用堆调整算法,使其与子树形成大顶堆。 +6. 节点序号减 `1`,对序号为 `1` 的节点,调用堆调整算法,使其与子树形成大顶堆。 +7. 此时整个原始序列对应的完全二叉树就成了一个大顶堆,建立初始堆完毕。 + +## 4. 堆排序方法完整演示 ![](http://qcdn.itcharge.cn/images/20211019172547.gif) -## 4. 堆排序算法分析 +1. 原始序列为 `[2, 7, 26, 25, 19, 17, 1, 90, 3, 36]`,先根据原始序列建立一个初始堆。 +2. 交换序列中第 `1` 个元素(`90`)与第 `10` 个元素(`2`)的位置。将序列前 `9` 个元素组成的子序列调整成一个大顶堆,此时堆顶变为 `36`。 +3. 交换序列中第 `1` 个元素(`36`)与第 `9` 个元素(`3`)的位置。将序列前 `8` 个元素组成的子序列调整成一个大顶堆,此时堆顶变为 `26`。 +4. 交换序列中第 `1` 个元素(`26`)与第 `8` 个元素(`2`)的位置。将序列前 `7` 个元素组成的子序列调整成一个大顶堆,此时堆顶变为 `25`。 +5. 以此类推,不断交换子序列的第 `1` 个元素(最大值元素)与当前子序列最后一个元素位置,并将其调整成新的大顶堆。直到子序列只剩下最后一个元素 `1` 时,排序结束。此时整个序列变成了一个有序序列,即 `[1, 2, 3, 7, 17, 19, 25, 26, 36, 90]`。 + +## 5. 堆排序算法分析 -- 堆积排序的时间主要花费在两个方面: - - 将原始序列构造为一个初始堆积。 - - 排序过程中不断将移走最大值元素,然后将剩下元素重新调整为一个新堆积。 -- 设原始序列所对应的完全二叉树深度为 d,算法由两个独立的循环组成: - - 在第 `1` 个循环构造初始堆积时,从 `i = d - 1` 层开始,到 `i = 1` 层为止,对每个分支节点都要调用一次 `adjust` 算法,每一次 `adjust` 算法,对于第 `i` 层一个节点到第 `d` 层上建立的子堆积,所有节点可能移动的最大距离为该子堆积根节点移动到最后一层(第 `d` 层) 的距离即 `d - i`。 - - 而第 `i` 层上节点最多有 2ドル^{i-1}$ 个,所以每一次 `adjust` 算法最大移动距离为 2ドル^{i-1} * (d-i)$。因此,堆积排序算法的第 `1` 个循环所需时间应该是各层上的节点数与该层上节点可移动的最大距离之积的总和,即:$\sum_{i = d - 1}^1 2^{i-1} (d-i) = \sum_{j = 1}^{d-1} 2^{d-j-1} \times j = \sum_{j = 1}^{d-1} 2^{d-1} \times {j \over 2^j} \le n \sum_{j = 1}^{d-1} {j \over 2^j} < 2n$。这一部分时间花费为 $O(n)$。 - - 在算法的第 `2` 个循环中每次调用 `adjust` 算法一次,节点移动的最大距离为这棵完全二叉树的深度 $d = \lfloor log_2(n) \rfloor + 1,ドル一共调用了 `n - 1` 次 `adjust` 算法,所以,第 `2` 个循环的时间花费为 $(n-1)(\lfloor log_2 (n)\rfloor + 1) = O(n log_2 n)$。 -- 因此,堆积排序的时间复杂度为 $O(nlog_2 n)$。 -- 由于在堆积排序中只需要一个记录大小的辅助空间,因此,堆积排序的空间复杂度为:$O(1)$。 -- 堆排序属于 **不稳定排序算法**。堆排序也是一种不适合在链表上实现的排序。 +- **时间复杂度**:$O(n \times \log_2 n)$。 + - 堆积排序的时间主要花费在两个方面:「建立初始堆」和「调整堆」。 + - 设原始序列所对应的完全二叉树深度为 $d,ドル算法由两个独立的循环组成: + 1. 在第 1ドル$ 个循环构造初始堆积时,从 $i = d - 1$ 层开始,到 $i = 1$ 层为止,对每个分支节点都要调用一次调整堆算法,而一次调整堆算法,对于第 $i$ 层一个节点到第 $d$ 层上建立的子堆积,所有节点可能移动的最大距离为该子堆积根节点移动到最后一层(第 $d$ 层) 的距离,即 $d - i$。而第 $i$ 层上节点最多有 2ドル^{i-1}$ 个,所以每一次调用调整堆算法的最大移动距离为 2ドル^{i-1} * (d-i)$。因此,堆积排序算法的第 1ドル$ 个循环所需时间应该是各层上的节点数与该层上节点可移动的最大距离之积的总和,即:$\sum_{i = d - 1}^1 2^{i-1} (d-i) = \sum_{j = 1}^{d-1} 2^{d-j-1} \times j = \sum_{j = 1}^{d-1} 2^{d-1} \times {j \over 2^j} \le n \sum_{j = 1}^{d-1} {j \over 2^j} < 2n$。这一部分的时间花费为 $O(n)$。 + 2. 在第 2ドル$ 个循环中,每次调用调整堆算法一次,节点移动的最大距离为这棵完全二叉树的深度 $d = \lfloor \log_2(n) \rfloor + 1,ドル一共调用了 $n - 1$ 次调整堆算法,所以,第 2ドル$ 个循环的时间花费为 $(n-1)(\lfloor \log_2 (n)\rfloor + 1) = O(n \times \log_2 n)$。 + - 因此,堆积排序的时间复杂度为 $O(n \times \log_2 n)$。 +- **空间复杂度**:$O(1)$。由于在堆积排序中只需要一个记录大小的辅助空间,因此,堆积排序的空间复杂度为:$O(1)$。 +- **排序稳定性**:堆排序是一种 **不稳定排序算法**。 -## 5. 堆排序代码实现 +## 6. 堆排序代码实现 ```Python class Solution: # 调整为大顶堆 def heapify(self, arr: [int], index: int, end: int): + # 根节点为 index,左节点为 2 * index + 1, 右节点为 2 * index + 2 left = index * 2 + 1 right = left + 1 while left <= end: @@ -87,7 +109,7 @@ class Solution: # 初始化大顶堆 def buildMaxHeap(self, arr: [int]): size = len(arr) - # (size-2) // 2 是最后一个非叶节点,叶节点不用调整 + # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 for i in range((size - 2) // 2, -1, -1): self.heapify(arr, i, size - 1) return arr From 5d4d642b8e9eda23e551b618c49ca50d8175a963 Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月18日 17:01:19 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A2=98=E8=A7=A3?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Contents/00.Introduction/04.Solutions-List.md | 3 +- README.md | 2 +- ...76345円274円217円345円214円271円351円205円215円.md" | 6 +- ...028. 345円256円236円347円216円260円 strStr().md" | 2 +- ...350267円203円346円270円270円346円210円217円 II.md" | 4 +- ...63350円267円203円346円270円270円346円210円217円.md" | 6 +- ...14347円273円264円347円237円251円351円230円265円.md" | 2 +- .... 347円247円273円345円212円250円351円233円266円.md" | 60 +++- ...14347円276円216円347円237円251円345円275円242円.md" | 2 +- ...04350円267円257円345円276円204円346円225円260円.md" | 2 +- ...41350円233円213円346円216円211円350円220円275円.md" | 4 +- ...22345円272円217円346円225円260円347円273円204円.md" | 340 ++++++++++++++---- ...04346円234円200円345円244円247円345円222円214円.md" | 5 +- ...00345円260円217円347円232円204円346円225円260円.md" | 5 + 14 files changed, 345 insertions(+), 98 deletions(-) diff --git a/Contents/00.Introduction/04.Solutions-List.md b/Contents/00.Introduction/04.Solutions-List.md index 96c2b240..23201773 100644 --- a/Contents/00.Introduction/04.Solutions-List.md +++ b/Contents/00.Introduction/04.Solutions-List.md @@ -1,4 +1,4 @@ -# LeetCode 题解(已完成 730 道) +# LeetCode 题解(已完成 731 道) | 题号 | 标题 | 题解 | 标签 | 难度 | | :------ | :------ | :------ | :------ | :------ | @@ -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) | 贪心算法、数组、动态规划 | 中等 | diff --git a/README.md b/README.md index 89adaead..d74141ea 100644 --- a/README.md +++ b/README.md @@ -254,4 +254,4 @@ - [动态规划优化题目](./Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) ## 11. 附加内容 -## [12. LeetCode 题解(已完成 730 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file +## [12. LeetCode 题解(已完成 731 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file diff --git "a/Solutions/0010. 346円255円243円345円210円231円350円241円250円350円276円276円345円274円217円345円214円271円351円205円215円.md" "b/Solutions/0010. 346円255円243円345円210円231円350円241円250円350円276円276円345円274円217円345円214円271円351円205円215円.md" index 54664a9b..a416a10e 100644 --- "a/Solutions/0010. 346円255円243円345円210円231円350円241円250円350円276円276円345円274円217円345円214円271円351円205円215円.md" +++ "b/Solutions/0010. 346円255円243円345円210円231円350円241円250円350円276円276円345円274円217円345円214円271円351円205円215円.md" @@ -51,9 +51,9 @@ - 如果 `p[j - 1] == '*'`,则我们可以对字符 `p[j - 2]` 进行 `0` ~ 若干次数的匹配。 - 如果 `s[i - 1] != p[j - 2]` 并且 `p[j - 2] != '.'`,则说明当前星号匹配不上,只能匹配 `0` 次(即匹配空字符串),则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 2` 个字符是否匹配」,即 `dp[i][j] = dp[i][j - 2] `。 - 如果 `s[i - 1] == p[j - 2]` 或者 `p[j - 2] == '.'`,则说明当前星号前面的字符 `p[j - 2]` 可以匹配 `s[i - 1]`。 - - 如果匹配 `0` 个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 2` 个字符是否匹配」。即 `dp[i][j] = dp[i][j - 2]`。 - - 如果匹配 `1` 个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」。即 `dp[i][j] = dp[i][j - 1] `。 - - 如果匹配多个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」。即 `dp[i][j] = dp[i - 1][j]`。 + - 如果匹配 `0` 个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 2` 个字符是否匹配」。即 `dp[i][j] = dp[i][j - 2]`。 + - 如果匹配 `1` 个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j - 1` 个字符是否匹配」。即 `dp[i][j] = dp[i][j - 1] `。 + - 如果匹配多个,则「字符串 `s` 的前 `i` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」取决于「字符串 `s` 的前 `i - 1` 个字符与字符串 `p` 的前 `j` 个字符是否匹配」。即 `dp[i][j] = dp[i - 1][j]`。 ###### 4. 初始条件 diff --git "a/Solutions/0028. 345円256円236円347円216円260円 strStr().md" "b/Solutions/0028. 345円256円236円347円216円260円 strStr().md" index 33c21a96..6f48d148 100644 --- "a/Solutions/0028. 345円256円236円347円216円260円 strStr().md" +++ "b/Solutions/0028. 345円256円236円347円216円260円 strStr().md" @@ -209,7 +209,7 @@ class Solution: bc_table = dict() for i in range(m - 1): - bc_table[p[i]] = m - i - 1 # 更新坏字符在模式串中最后一次出现的位置 + bc_table[p[i]] = m - i - 1 # 更新坏字符在模式串中最后一次出现的位置 return bc_table return horspool(haystack, needle) diff --git "a/Solutions/0045. 350円267円263円350円267円203円346円270円270円346円210円217円 II.md" "b/Solutions/0045. 350円267円263円350円267円203円346円270円270円346円210円217円 II.md" index 066f9d57..6f93ec9d 100644 --- "a/Solutions/0045. 350円267円263円350円267円203円346円270円270円346円210円217円 II.md" +++ "b/Solutions/0045. 350円267円263円350円267円203円346円270円270円346円210円217円 II.md" @@ -121,8 +121,8 @@ class Solution: 1. 维护几个变量:当前所能达到的最远位置 `end`,下一步所能跳到的最远位置 `max_pos`,最少跳跃次数 `setps`。 2. 遍历数组 `nums` 的前 `len(nums) - 1` 个元素: - 1. 每次更新第 `i` 位置下一步所能跳到的最远位置 `max_pos`。 - 2. 如果索引 `i` 到达了 `end` 边界,则:更新 `end` 为新的当前位置 `max_pos`,并令步数 `setps` 加 `1`。 + 1. 每次更新第 `i` 位置下一步所能跳到的最远位置 `max_pos`。 + 2. 如果索引 `i` 到达了 `end` 边界,则:更新 `end` 为新的当前位置 `max_pos`,并令步数 `setps` 加 `1`。 3. 最终返回跳跃次数 `steps`。 ### 思路 2:贪心算法代码 diff --git "a/Solutions/0055. 350円267円263円350円267円203円346円270円270円346円210円217円.md" "b/Solutions/0055. 350円267円263円350円267円203円346円270円270円346円210円217円.md" index 805df09b..843e9d2d 100644 --- "a/Solutions/0055. 350円267円263円350円267円203円346円270円270円346円210円217円.md" +++ "b/Solutions/0055. 350円267円263円350円267円203円346円270円270円346円210円217円.md" @@ -26,10 +26,10 @@ class Solution: dp = [0 for _ in range(size)] dp[0] = nums[0] for i in range(1, size): - if i <= dp[i-1]: - dp[i] = max(dp[i-1], i + nums[i]) + if i <= dp[i - 1]: + dp[i] = max(dp[i - 1], i + nums[i]) else: - dp[i] = dp[i-1] + dp[i] = dp[i - 1] return dp[-1]>= size - 1 ``` diff --git "a/Solutions/0074. 346円220円234円347円264円242円344円272円214円347円273円264円347円237円251円351円230円265円.md" "b/Solutions/0074. 346円220円234円347円264円242円344円272円214円347円273円264円347円237円251円351円230円265円.md" index 9172def0..46b83cef 100644 --- "a/Solutions/0074. 346円220円234円347円264円242円344円272円214円347円273円264円347円237円251円351円230円265円.md" +++ "b/Solutions/0074. 346円220円234円347円264円242円344円272円214円347円273円264円347円237円251円351円230円265円.md" @@ -50,7 +50,7 @@ class Solution: else: right = mid return left - + def rowBinarySearch(self, matrix, begin, cols, target): left = begin right = cols diff --git "a/Solutions/0283. 347円247円273円345円212円250円351円233円266円.md" "b/Solutions/0283. 347円247円273円345円212円250円351円233円266円.md" index aecaf171..51027d28 100644 --- "a/Solutions/0283. 347円247円273円345円212円250円351円233円266円.md" +++ "b/Solutions/0283. 347円247円273円345円212円250円351円233円266円.md" @@ -5,19 +5,62 @@ ## 题目大意 -给你一个数组,将所有 0 移动到末尾,并保持原有的非 0 数字的相对顺序。要求只能在原数组上进行操作。 +**描述**:给定一个数组 `nums`。 + +**要求**:将所有 0 移动到末尾,并保持原有的非 0 数字的相对顺序。 + +**说明**: + +- 只能在原数组上进行操作。 +- 1ドル \le nums.length \le 10^4$。 +- $-2^{31} \le nums[i] \le 2^{31} - 1$。 + +**示例**: + +```Python +输入: nums = [0,1,0,3,12] +输出: [1,3,12,0,0] + + +输入: nums = [0] +输出: [0] +``` ## 解题思路 -使用两个指针 left,right。left 指向处理好的非 0 数字数组的尾部,right 指针指向当前待处理元素。 +### 思路 1:冒泡排序(超时) + +冒泡排序的思想,就是通过相邻元素的比较与交换,使得较大元素从前面移到后面。 + +我们可以借用冒泡排序的思想,将值为 `0` 的元素移动到数组末尾。 -不断向右移动 right 指针,每次移动到非零数,则将左右指针对应的数交换,交换同时将 left 右移。 +因为数据规模为 10ドル^4,ドル而冒泡排序的时间复杂度为 $O(n^2)$。所以这种做法会导致超时。 -此时,left 指针左边均为处理好的非零数,而从 left 指针指向的位置开始, right 指针左边都为 0。 +### 思路 1:代码 -遍历结束之后,则所有 0 都移动到了右侧,且保持了非零数的相对位置。 +```Python +class Solution: + def moveZeroes(self, nums: List[int]) -> None: + for i in range(len(nums)): + for j in range(len(nums) - i - 1): + if nums[j] == 0 and nums[j + 1] != 0: + nums[j], nums[j + 1] = nums[j + 1], nums[j] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 -## 代码 +### 思路 2:双指针 + +1. 使用两个指针 `left`,`right`。`left` 指向处理好的非 `0` 数字数组的尾部,`right` 指针指向当前待处理元素。 +2. 不断向右移动 `right` 指针,每次移动到非零数,则将左右指针对应的数交换,交换同时将 `left` 右移。 +3. 此时,`left` 指针左侧均为处理好的非零数,而从 `left` 指针指向的位置开始, `right` 指针左边为止都为 `0`。 + +遍历结束之后,则所有 `0` 都移动到了右侧,且保持了非零数的相对位置。 + +### 思路 2:代码 ```Python class Solution: @@ -31,3 +74,8 @@ class Solution: right += 1 ``` +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/0391. 345円256円214円347円276円216円347円237円251円345円275円242円.md" "b/Solutions/0391. 345円256円214円347円276円216円347円237円251円345円275円242円.md" index fe05030c..964a6d40 100644 --- "a/Solutions/0391. 345円256円214円347円276円216円347円237円251円345円275円242円.md" +++ "b/Solutions/0391. 345円256円214円347円276円216円347円237円251円345円275円242円.md" @@ -138,7 +138,7 @@ class SegmentTree: self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val else: self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val - self.tree[index].val += val # 当前节点所在区间每个元素值增加 val + self.tree[index].val += val # 当前节点所在区间每个元素值增加 val return if right < q_left or left> q_right: # 节点所在区间与 [q_left, q_right] 无关 diff --git "a/Solutions/0576. 345円207円272円347円225円214円347円232円204円350円267円257円345円276円204円346円225円260円.md" "b/Solutions/0576. 345円207円272円347円225円214円347円232円204円350円267円257円345円276円204円346円225円260円.md" index da419857..b09ec625 100644 --- "a/Solutions/0576. 345円207円272円347円225円214円347円232円204円350円267円257円345円276円204円346円225円260円.md" +++ "b/Solutions/0576. 345円207円272円347円225円214円347円232円204円350円267円257円345円276円204円346円225円260円.md" @@ -64,7 +64,7 @@ class Solution: mod = 10 ** 9 + 7 dp = [[[0 for _ in range(maxMove + 1)] for _ in range(n)] for _ in range(m)] - for i in r + for i in r for k in range(1, maxMove + 1): for i in range(m): for j in range(n): diff --git "a/Solutions/0887. 351円270円241円350円233円213円346円216円211円350円220円275円.md" "b/Solutions/0887. 351円270円241円350円233円213円346円216円211円350円220円275円.md" index e8d12180..0647aea1 100644 --- "a/Solutions/0887. 351円270円241円350円233円213円346円216円211円350円220円275円.md" +++ "b/Solutions/0887. 351円270円241円350円233円213円346円216円211円350円220円275円.md" @@ -106,8 +106,8 @@ $dp[i][j] = min_{1 \le x \le n} (max(dp[i - x][j], dp[x - 1][j - 1])) + 1$ - 当鸡蛋数为 `1` 时,`dp[i][1] = i`。这是如果唯一的蛋碎了,则无法测试了。只能从低到高,一步步进行测试,最终最少测试数为当前拥有的楼层数(如果刚开始初始化时已经将所有值设置为当前拥有的楼层数,其实这一步可省略)。 - 当楼层为 `1` 时,在 `1` 层扔鸡蛋,`dp[1][j] = 1`。这是因为: - - 如果在 `1` 层扔鸡蛋碎了,则 `f < 1`。同时因为 `f` 的取值范围为 `[0, n]`。所以能确定 `f = 0`。 - - 如果在 `1` 层扔鸡蛋没碎,则 `f>= 1`。同时因为 `f` 的取值范围为 `[0, n]`。所以能确定 `f = 0`。 + - 如果在 `1` 层扔鸡蛋碎了,则 `f < 1`。同时因为 `f` 的取值范围为 `[0, n]`。所以能确定 `f = 0`。 + - 如果在 `1` 层扔鸡蛋没碎,则 `f>= 1`。同时因为 `f` 的取值范围为 `[0, n]`。所以能确定 `f = 0`。 ###### 5. 最终结果 diff --git "a/Solutions/0912. 346円216円222円345円272円217円346円225円260円347円273円204円.md" "b/Solutions/0912. 346円216円222円345円272円217円346円225円260円347円273円204円.md" index 87d8a2b5..3259f811 100644 --- "a/Solutions/0912. 346円216円222円345円272円217円346円225円260円347円273円204円.md" +++ "b/Solutions/0912. 346円216円222円345円272円217円346円225円260円347円273円204円.md" @@ -5,166 +5,294 @@ ## 题目大意 -给你一个整数数组 `nums`。 +**描述**:给定一个整数数组 `nums`。 -要求:将该数组升序排列。 +**要求**:将该数组升序排列。 + +**说明**: + +- 1ドル \le nums.length \le 5 * 10^4$。 +- $-5 * 10^4 \le nums[i] \le 5 * 10^4$。 + +**示例**: + +```Python +输入:nums = [5,2,3,1] +输出:[1,2,3,5] + + +输入:nums = [5,1,1,2,0,0] +输出:[0,0,1,1,2,5] +``` ## 解题思路 这道题是一道用来复习排序算法,测试算法时间复杂度的好题。我试过了十种排序算法。得到了如下结论: -- 超时算法(时间复杂度为 $O(n^2)$):冒泡排序、选择排序、插入排序。没毛病,这些都是时间复杂度 $O(n^2)$ 的排序算法。 -- 通过算法(时间复杂度为 $O(n\log n)$):希尔排序、归并排序、快速排序、堆排序 -- 通过算法(时间复杂度为 $O(n)$):计数排序、桶排序 -- 解答错误算法(普通基数排序只适合非负数):基数排序 +- 超时算法(时间复杂度为 $O(n^2)$):冒泡排序、选择排序、插入排序。 +- 通过算法(时间复杂度为 $O(n \times \log_2 n)$):希尔排序、归并排序、快速排序、堆排序。 +- 通过算法(时间复杂度为 $O(n)$):计数排序、桶排序。 +- 解答错误算法(普通基数排序只适合非负数):基数排序。 + +### 思路 1:冒泡排序(超时) -下面给出这十种排序算法相关的代码。 +**冒泡排序(Bubble Sort)基本思想**: -## 代码 +- 第 `i (i = 1, 2, ...)` 趟排序时从序列中前 `n - i + 1` 个元素的第 `1` 个元素开始,相邻两个元素进行比较,如果前者大于后者,两者交换位置,否则不交换。 -### 1. 冒泡排序 +### 思路 1:代码 ```Python class Solution: def bubbleSort(self, arr): + # 第 i 趟排序 for i in range(len(arr)): + # 从序列中前 n - i + 1 个元素的第 1 个元素开始,相邻两个元素进行比较 for j in range(len(arr) - i - 1): + # 相邻两个元素进行比较,如果前者大于后者,则交换位置 if arr[j]> arr[j + 1]: arr[j], arr[j + 1] = arr[j + 1], arr[j] - + return arr - + def sortArray(self, nums: List[int]) -> List[int]: return self.bubbleSort(nums) ``` -### 2. 选择排序 +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:选择排序(超时) + +**选择排序(Selection Sort)基本思想**: + +- 将序列分为两部分:前边 `i - 1` 个元素为已排序部分,后边 `n - i + 1` 个元素为未排序部分。 +- 第 `i` 趟排序从未排序部分 `n − i + 1 (i = 1, 2, ..., n − 1)` 个元素中选择一个值最小的元素与未排序部分最前面那个元素交换位置,即与整个序列的第 `i` 个位置上的元素交换位置。 +- 如此下去,直到所有元素都变为已排序部分,排序结束。 + +### 思路 2:代码 ```Python class Solution: def selectionSort(self, arr): for i in range(len(arr) - 1): - # 记录未排序序列中最小数的索引 + # 记录未排序部分中最小值的位置 min_i = i for j in range(i + 1, len(arr)): if arr[j] < arr[min_i]: min_i = j - # 如果找到最小数,将 i 位置上元素与最小数位置上元素进行交换 + # 如果找到最小值的位置,将 i 位置上元素与最小值位置上的元素进行交换 if i != min_i: arr[i], arr[min_i] = arr[min_i], arr[i] return arr - + def sortArray(self, nums: List[int]) -> List[int]: return self.selectionSort(nums) ``` -### 3. 插入排序 +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 3:插入排序(超时) + +**插入排序(Insertion Sort)基本思想**: + +- 将整个序列分为两部分:前面 `i` 个元素为有序序列,后面 `n - i` 个元素为无序序列。 +- 每一次排序,将无序序列的第 `1` 个元素,在有序序列中找到相应的位置并插入。 + +### 思路 3:代码 ```Python class Solution: def insertionSort(self, arr): + # 遍历无序序列 for i in range(1, len(arr)): temp = arr[i] j = i + # 从右至左遍历有序序列 while j> 0 and arr[j - 1]> temp: + # 将有序序列中插入位置右侧的元素依次右移一位 arr[j] = arr[j - 1] j -= 1 + # 将该元素插入到适当位置 arr[j] = temp - + return arr - + def sortArray(self, nums: List[int]) -> List[int]: return self.insertionSort(nums) ``` -### 4. 希尔排序 +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 4:希尔排序(通过) + +**希尔排序(Shell Sort)基本思想**: + +- 将整个序列切按照一定的间隔取值划分为若干个子序列,每个子序列分别进行插入排序。 +- 然后逐渐缩小间隔进行下一轮划分子序列和对子序列进行插入排序。 +- 直至最后一轮排序间隔为 `1`,对整个序列进行插入排序,完成排序。 + +### 思路 4:代码 ```Python class Solution: def shellSort(self, arr): size = len(arr) gap = size // 2 - + # 按照 gap 分组 while gap> 0: + # 对每组元素进行插入排序 for i in range(gap, size): + # temp 为每组中无序序列第 1 个元素 temp = arr[i] j = i + # 从右至左遍历每组中的有序序列元素 while j>= gap and arr[j - gap]> temp: + # 将每组有序序列中插入位置右侧的元素依次在组中右移一位 arr[j] = arr[j - gap] j -= gap + # 将该元素插入到适当位置 arr[j] = temp + # 缩小 gap 间隔 gap = gap // 2 return arr - + def sortArray(self, nums: List[int]) -> List[int]: return self.shellSort(nums) ``` -### 5. 归并排序 +### 思路 4:复杂度分析 + +- **时间复杂度**:介于 $O(n \times \log_2 n)$ 与 $O(n^2)$ 之间。 +- **空间复杂度**:$O(1)$。 + +### 思路 5:归并排序(通过) + +**归并排序(Merge Sort)基本思想**: + +- 采用经典的分治策略,先递归地将当前序列平均分成两半。 +- 然后将有序序列两两合并,最终合并成一个有序序列。 + +### 思路 5:代码 ```Python class Solution: - def merge(self, left_arr, right_arr): + def merge(self, left_arr, right_arr): # 归并过程 arr = [] - while left_arr and right_arr: - if left_arr[0] <= right_arr[0]: - arr.append(left_arr.pop(0)) + left_i, right_i = 0, 0 + while left_i < len(left_arr) and right_i < len(right_arr): + # 将两个有序子序列中较小元素依次插入到结果数组中 + if left_arr[left_i] < right_arr[right_i]: + arr.append(left_arr[left_i]) + left_i += 1 else: - arr.append(right_arr.pop(0)) - while left_arr: - arr.append(left_arr.pop(0)) - while right_arr: - arr.append(right_arr.pop(0)) - return arr + arr.append(right_arr[right_i]) + right_i += 1 + + while left_i < len(left_arr): + # 如果左子序列有剩余元素,则将其插入到结果数组中 + arr.append(left_arr[left_i]) + left_i += 1 + + while right_i < len(right_arr): + # 如果右子序列有剩余元素,则将其插入到结果数组中 + arr.append(right_arr[right_i]) + right_i += 1 + + return arr # 返回排好序的结果数组 - def mergeSort(self, arr): - size = len(arr) - if size < 2: + def mergeSort(self, arr): # 分割过程 + if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组 return arr - mid = len(arr) // 2 - left_arr, right_arr = arr[0: mid], arr[mid:] - return self.merge(self.mergeSort(left_arr), self.mergeSort(right_arr)) - + + mid = len(arr) // 2 # 将数组从中间位置分为左右两个数组。 + left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分割和排序 + right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分割和排序 + return self.merge(left_arr, right_arr) # 把当前序列组中有序子序列逐层向上,进行两两合并。 + def sortArray(self, nums: List[int]) -> List[int]: return self.mergeSort(nums) ``` -### 6. 快速排序 +### 思路 5:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 6:快速排序(通过) + +**快速排序(Quick Sort)基本思想**: + +- 通过一趟排序将无序序列分为独立的两个序列,第一个序列的值均比第二个序列的值小。 +- 然后递归地排列两个子序列,以达到整个序列有序。 + +### 思路 6:代码 ```Python import random - + class Solution: - def randomPartition(self, arr, low, high): + # 从 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, low, high): - x = arr[high] - i = low - 1 - for j in range(low, high): - if arr[j] <= arr[high]: - i += 1 + + # 以最低位为基准数,然后将序列中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上 + def partition(self, arr: [int], low: int, high: int): + 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 quickDivideSort(self, arr, low, high): - n = len(arr) + def quickSort(self, arr, low, high): if low < high: + # 按照基准数的位置,将序列划分为左右两个子序列 pi = self.randomPartition(arr, low, high) - self.quickDivideSort(arr, low, pi - 1) - self.quickDivideSort(arr, pi + 1, high) + # 对左右两个子序列分别进行递归快速排序 + self.quickSort(arr, low, pi - 1) + self.quickSort(arr, pi + 1, high) return arr - def quickSort(self, arr): - low, high = 0, len(arr) - 1 - return self.quickDivideSort(arr, low, high) + def sortArray(self, nums: List[int]) -> List[int]: + return self.quickSort(nums, 0, len(nums) - 1) ``` -### 7. 堆排序 +### 思路 6:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2 n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 7:堆排序(通过) + +**堆排序(Heap sort)基本思想**: + +- 借用「堆结构」所设计的排序算法。 +- 将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆结构继续维持大顶堆性质。 + +### 思路 7:代码 ```Python class Solution: @@ -213,84 +341,146 @@ class Solution: return self.maxHeapSort(nums) ``` -### 8. 计数排序 +### 思路 7:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2 n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 8:计数排序(通过) + +**计数排序(Counting Sort)基本思想**: + +- 使用一个额外的数组 `counts`,其中 `counts[i]` 表示原数组 `arr` 中值等于 `i` 的元素个数。 +- 然后根据数组 `counts` 来将 `arr` 中的元素排到正确的位置。 + +### 思路 8:代码 ```Python class Solution: def countingSort(self, arr): + # 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min arr_min, arr_max = min(arr), max(arr) + # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 size = arr_max - arr_min + 1 counts = [0 for _ in range(size)] + # 统计值为 num 的元素出现的次数 for num in arr: counts[num - arr_min] += 1 + + # 计算元素排名 for j in range(1, size): counts[j] += counts[j - 1] - + + # 反向填充目标数组 res = [0 for _ in range(len(arr))] for i in range(len(arr) - 1, -1, -1): + # 根据排名,将 arr[i] 放在数组对应位置 res[counts[arr[i] - arr_min] - 1] = arr[i] + # 将 arr[i] 的对应排名减 1 counts[arr[i] - arr_min] -= 1 - + return res def sortArray(self, nums: List[int]) -> List[int]: return self.countingSort(nums) ``` -### 9. 桶排序 +### 思路 8:复杂度分析 + +- **时间复杂度**:$O(n + k)$。其中 $k$ 代表待排序序列的值域。 +- **空间复杂度**:$O(k)$。其中 $k$ 代表待排序序列的值域。 + +### 思路 9:桶排序(通过) + +**桶排序(Bucket Sort)基本思想**: + +- 将未排序数组分到若干个「桶」中,每个桶的元素再进行单独排序。 + +### 思路 9:代码 ```Python class Solution: def insertionSort(self, arr): + # 遍历无序序列 for i in range(1, len(arr)): temp = arr[i] j = i + # 从右至左遍历有序序列 while j> 0 and arr[j - 1]> temp: + # 将有序序列中插入位置右侧的元素依次右移一位 arr[j] = arr[j - 1] j -= 1 + # 将该元素插入到适当位置 arr[j] = temp - + return arr - - def bucketSort(self, arr, bucket_size = 5): + + def bucketSort(self, arr, bucket_size=5): + # 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min arr_min, arr_max = min(arr), max(arr) + # 定义桶的个数为 (最大值元素 - 最小值元素) // 每个桶的大小 + 1 bucket_count = (arr_max - arr_min) // bucket_size + 1 + # 定义桶数组 buckets buckets = [[] for _ in range(bucket_count)] - + + # 遍历原始数组元素,将每个元素装入对应区间的桶中 for num in arr: buckets[(num - arr_min) // bucket_size].append(num) - + + # 对每个桶内的元素单独排序,并合并到 res 数组中 res = [] for bucket in buckets: self.insertionSort(bucket) res.extend(bucket) - return res + return res def sortArray(self, nums: List[int]) -> List[int]: return self.bucketSort(nums) ``` -### 10. 基数排序 +### 思路 9:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n + m)$。$m$ 为桶的个数。 + +### 思路 10:基数排序(提交解答错误,普通基数排序只适合非负数) + +**基数排序(Radix Sort)基本思想**: + +- 将整数按位数切割成不同的数字,然后按每个位数分别比较进行排序。 + +### 思路 10:代码 ```Python class Solution: def radixSort(self, arr): + # 桶的大小为所有元素的最大位数 size = len(str(max(arr))) - + + # 从低位到高位依次遍历每一位,以各个数位值为索引,对数组进行按数位排序 for i in range(size): + # 使用一个长度为 10 的桶来存放各个位上的元素 buckets = [[] for _ in range(10)] + # 遍历数组元素,根据元素对应位上的值,将其存入对应位的桶中 for num in arr: - buckets[num // (10**i) % 10].append(num) + buckets[num // (10 ** i) % 10].append(num) + # 清空原始数组 arr.clear() + # 从桶中依次取出对应元素,并重新加入到原始数组 for bucket in buckets: for num in bucket: arr.append(num) - - return arr + + return arr def sortArray(self, nums: List[int]) -> List[int]: return self.radixSort(nums) ``` +### 思路 10:复杂度分析 + +- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 +- **空间复杂度**:$O(n + k)$。 + diff --git "a/Solutions/345円211円221円346円214円207円 Offer 42. 350円277円236円347円273円255円345円255円220円346円225円260円347円273円204円347円232円204円346円234円200円345円244円247円345円222円214円.md" "b/Solutions/345円211円221円346円214円207円 Offer 42. 350円277円236円347円273円255円345円255円220円346円225円260円347円273円204円347円232円204円346円234円200円345円244円247円345円222円214円.md" index e6d735f3..16a7508a 100644 --- "a/Solutions/345円211円221円346円214円207円 Offer 42. 350円277円236円347円273円255円345円255円220円346円225円260円347円273円204円347円232円204円346円234円200円345円244円247円345円222円214円.md" +++ "b/Solutions/345円211円221円346円214円207円 Offer 42. 350円277円236円347円273円255円345円255円220円346円225円260円347円273円204円347円232円204円346円234円200円345円244円247円345円222円214円.md" @@ -15,7 +15,10 @@ 假设 f(i) 表示第 i 个数结尾的「连续子数组的最大和」,那么 $max_{0 < i \le n-1} {f(i)} = max(f(i-1) + nums[i], nums[i])$ -即将之前累加和加上当前值与当前值做比较, 如果将之前累加和加上当前值> 当前值,那么加上当前值;如果将之前累加和加上当前值 < 当前值,那么 $f(i) = nums[i]$。 +即将之前累加和加上当前值与当前值做比较。 + +- 如果将之前累加和加上当前值> 当前值,那么加上当前值。 +- 如果将之前累加和加上当前值 < 当前值,那么 $f(i) = nums[i]$。 ## 代码 diff --git "a/Solutions/345円211円221円346円214円207円 Offer 45. 346円212円212円346円225円260円347円273円204円346円216円222円346円210円220円346円234円200円345円260円217円347円232円204円346円225円260円.md" "b/Solutions/345円211円221円346円214円207円 Offer 45. 346円212円212円346円225円260円347円273円204円346円216円222円346円210円220円346円234円200円345円260円217円347円232円204円346円225円260円.md" index 38c97857..e73c9731 100644 --- "a/Solutions/345円211円221円346円214円207円 Offer 45. 346円212円212円346円225円260円347円273円204円346円216円222円346円210円220円346円234円200円345円260円217円347円232円204円346円225円260円.md" +++ "b/Solutions/345円211円221円346円214円207円 Offer 45. 346円212円212円346円225円260円347円273円204円346円216円222円346円210円220円346円234円200円345円260円217円347円232円204円346円225円260円.md" @@ -53,6 +53,11 @@ class Solution: return ''.join(nums_s) ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2n)$。排序算法的时间复杂度为 $O(n \times \log_2n)$。 +- **空间复杂度**:$O(1)$。 + ## 参考资料 - 【题解】[剑指 Offer 45. 把数组排成最小的数(自定义排序,清晰图解) - 把数组排成最小的数 - 力扣](https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/solution/mian-shi-ti-45-ba-shu-zu-pai-cheng-zui-xiao-de-s-4/) From 01a67f9dded950cb485ebb8373dcc228c4e40902 Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月18日 17:01:28 +0800 Subject: [PATCH 10/12] Update 06.Array-Quick-Sort.md --- Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md b/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md index f8654a21..2c049c25 100644 --- a/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md +++ b/Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md @@ -1,6 +1,6 @@ ## 1. 快速排序算法思想 -> 快速排序(Quick Sort)基本思想: +> **快速排序(Quick Sort)基本思想**: > > 通过一趟排序将无序序列分为独立的两个序列,第一个序列的值均比第二个序列的值小。然后递归地排列两个子序列,以达到整个序列有序。 From e78071c6d3104306d8c722725c520605c95f23ab Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月18日 17:01:34 +0800 Subject: [PATCH 11/12] Update 10.Array-Radix-Sort.md --- Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md b/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md index f4ff1781..2d80904c 100644 --- a/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md +++ b/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md @@ -28,7 +28,7 @@ ## 4. 基数排序算法分析 -- **时间复杂度**:$O(k * n)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 +- **时间复杂度**:$O(n \times k)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 - **空间复杂度**:$O(n + k)$。 - **排序稳定性**:基数排序是一种 **稳定排序算法**。 From 4f751795ccd954e2a6d1140952388eb415d86861 Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2022年8月18日 17:06:10 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Templates/01.Array/Array-BubbleSort.py | 3 +++ Templates/01.Array/Array-BucketSort.py | 9 +++++++ Templates/01.Array/Array-CountingSort.py | 10 ++++++- Templates/01.Array/Array-InsertionSort.py | 4 +++ Templates/01.Array/Array-MaxHeapSort.py | 31 ++++++++++----------- Templates/01.Array/Array-MergeSort.py | 4 +-- Templates/01.Array/Array-QuickSort.py | 33 +++++++++++++++-------- Templates/01.Array/Array-RadixSort.py | 6 +++++ Templates/01.Array/Array-SelectionSort.py | 4 +-- Templates/01.Array/Array-ShellSort.py | 8 +++++- 10 files changed, 80 insertions(+), 32 deletions(-) diff --git a/Templates/01.Array/Array-BubbleSort.py b/Templates/01.Array/Array-BubbleSort.py index cde737b5..350f3ea3 100644 --- a/Templates/01.Array/Array-BubbleSort.py +++ b/Templates/01.Array/Array-BubbleSort.py @@ -1,7 +1,10 @@ class Solution: def bubbleSort(self, arr): + # 第 i 趟排序 for i in range(len(arr)): + # 从序列中前 n - i + 1 个元素的第 1 个元素开始,相邻两个元素进行比较 for j in range(len(arr) - i - 1): + # 相邻两个元素进行比较,如果前者大于后者,则交换位置 if arr[j]> arr[j + 1]: arr[j], arr[j + 1] = arr[j + 1], arr[j] diff --git a/Templates/01.Array/Array-BucketSort.py b/Templates/01.Array/Array-BucketSort.py index 81d40cb0..02fea784 100644 --- a/Templates/01.Array/Array-BucketSort.py +++ b/Templates/01.Array/Array-BucketSort.py @@ -1,23 +1,32 @@ class Solution: def insertionSort(self, arr): + # 遍历无序序列 for i in range(1, len(arr)): temp = arr[i] j = i + # 从右至左遍历有序序列 while j> 0 and arr[j - 1]> temp: + # 将有序序列中插入位置右侧的元素依次右移一位 arr[j] = arr[j - 1] j -= 1 + # 将该元素插入到适当位置 arr[j] = temp return arr def bucketSort(self, arr, bucket_size=5): + # 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min arr_min, arr_max = min(arr), max(arr) + # 定义桶的个数为 (最大值元素 - 最小值元素) // 每个桶的大小 + 1 bucket_count = (arr_max - arr_min) // bucket_size + 1 + # 定义桶数组 buckets buckets = [[] for _ in range(bucket_count)] + # 遍历原始数组元素,将每个元素装入对应区间的桶中 for num in arr: buckets[(num - arr_min) // bucket_size].append(num) + # 对每个桶内的元素单独排序,并合并到 res 数组中 res = [] for bucket in buckets: self.insertionSort(bucket) diff --git a/Templates/01.Array/Array-CountingSort.py b/Templates/01.Array/Array-CountingSort.py index 1e88bcdc..05d8eb4e 100644 --- a/Templates/01.Array/Array-CountingSort.py +++ b/Templates/01.Array/Array-CountingSort.py @@ -1,17 +1,25 @@ class Solution: def countingSort(self, arr): + # 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min arr_min, arr_max = min(arr), max(arr) + # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 size = arr_max - arr_min + 1 counts = [0 for _ in range(size)] - + + # 统计值为 num 的元素出现的次数 for num in arr: counts[num - arr_min] += 1 + + # 计算元素排名 for j in range(1, size): counts[j] += counts[j - 1] + # 反向填充目标数组 res = [0 for _ in range(len(arr))] for i in range(len(arr) - 1, -1, -1): + # 根据排名,将 arr[i] 放在数组对应位置 res[counts[arr[i] - arr_min] - 1] = arr[i] + # 将 arr[i] 的对应排名减 1 counts[arr[i] - arr_min] -= 1 return res diff --git a/Templates/01.Array/Array-InsertionSort.py b/Templates/01.Array/Array-InsertionSort.py index 93dfaae5..9dfbb826 100644 --- a/Templates/01.Array/Array-InsertionSort.py +++ b/Templates/01.Array/Array-InsertionSort.py @@ -1,11 +1,15 @@ class Solution: def insertionSort(self, arr): + # 遍历无序序列 for i in range(1, len(arr)): temp = arr[i] j = i + # 从右至左遍历有序序列 while j> 0 and arr[j - 1]> temp: + # 将有序序列中插入位置右侧的元素依次右移一位 arr[j] = arr[j - 1] j -= 1 + # 将该元素插入到适当位置 arr[j] = temp return arr diff --git a/Templates/01.Array/Array-MaxHeapSort.py b/Templates/01.Array/Array-MaxHeapSort.py index 907b91b8..1e935777 100644 --- a/Templates/01.Array/Array-MaxHeapSort.py +++ b/Templates/01.Array/Array-MaxHeapSort.py @@ -1,44 +1,45 @@ class Solution: # 调整为大顶堆 - def heapify(self, nums: [int], index: int, end: int): + def heapify(self, arr: [int], index: int, end: int): + # 根节点为 index,左节点为 2 * index + 1, 右节点为 2 * index + 2 left = index * 2 + 1 right = left + 1 while left <= end: # 当前节点为非叶子结点 max_index = index - if nums[left]> nums[max_index]: + if arr[left]> arr[max_index]: max_index = left - if right <= end and nums[right]> nums[max_index]: + if right <= end and arr[right]> arr[max_index]: max_index = right if index == max_index: # 如果不用交换,则说明已经交换结束 break - nums[index], nums[max_index] = nums[max_index], nums[index] + arr[index], arr[max_index] = arr[max_index], arr[index] # 继续调整子树 index = max_index left = index * 2 + 1 right = left + 1 # 初始化大顶堆 - def buildMaxHeap(self, nums: [int]): - size = len(nums) + def buildMaxHeap(self, arr: [int]): + size = len(arr) # (size - 2) // 2 是最后一个非叶节点,叶节点不用调整 for i in range((size - 2) // 2, -1, -1): - self.heapify(nums, i, size - 1) - return nums + self.heapify(arr, i, size - 1) + return arr # 升序堆排序,思路如下: # 1. 先建立大顶堆 # 2. 让堆顶最大元素与最后一个交换,然后调整第一个元素到倒数第二个元素,这一步获取最大值 # 3. 再交换堆顶元素与倒数第二个元素,然后调整第一个元素到倒数第三个元素,这一步获取第二大值 # 4. 以此类推,直到最后一个元素交换之后完毕。 - def maxHeapSort(self, nums: [int]): - self.buildMaxHeap(nums) - size = len(nums) + def maxHeapSort(self, arr: [int]): + self.buildMaxHeap(arr) + size = len(arr) for i in range(size): - nums[0], nums[size - i - 1] = nums[size - i - 1], nums[0] - self.heapify(nums, 0, size - i - 2) - return nums + arr[0], arr[size - i - 1] = arr[size - i - 1], arr[0] + self.heapify(arr, 0, size - i - 2) + return arr - def sortnumsay(self, nums: List[int]) -> List[int]: + def sortArray(self, nums: List[int]) -> List[int]: return self.maxHeapSort(nums) \ No newline at end of file diff --git a/Templates/01.Array/Array-MergeSort.py b/Templates/01.Array/Array-MergeSort.py index da0cfeda..bd561ec2 100644 --- a/Templates/01.Array/Array-MergeSort.py +++ b/Templates/01.Array/Array-MergeSort.py @@ -1,5 +1,5 @@ class Solution: - def merge(self, left_arr, right_arr): # 归并 + def merge(self, left_arr, right_arr): # 归并过程 arr = [] left_i, right_i = 0, 0 while left_i < len(left_arr) and right_i < len(right_arr): @@ -23,7 +23,7 @@ def merge(self, left_arr, right_arr): # 归并 return arr # 返回排好序的结果数组 - def mergeSort(self, arr): # 分割 + def mergeSort(self, arr): # 分割过程 if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组 return arr diff --git a/Templates/01.Array/Array-QuickSort.py b/Templates/01.Array/Array-QuickSort.py index cb68dd08..ec940f4a 100644 --- a/Templates/01.Array/Array-QuickSort.py +++ b/Templates/01.Array/Array-QuickSort.py @@ -1,26 +1,37 @@ 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) diff --git a/Templates/01.Array/Array-RadixSort.py b/Templates/01.Array/Array-RadixSort.py index eeb37305..c4406c68 100644 --- a/Templates/01.Array/Array-RadixSort.py +++ b/Templates/01.Array/Array-RadixSort.py @@ -1,12 +1,18 @@ class Solution: def radixSort(self, arr): + # 桶的大小为所有元素的最大位数 size = len(str(max(arr))) + # 从低位到高位依次遍历每一位,以各个数位值为索引,对数组进行按数位排序 for i in range(size): + # 使用一个长度为 10 的桶来存放各个位上的元素 buckets = [[] for _ in range(10)] + # 遍历数组元素,根据元素对应位上的值,将其存入对应位的桶中 for num in arr: buckets[num // (10 ** i) % 10].append(num) + # 清空原始数组 arr.clear() + # 从桶中依次取出对应元素,并重新加入到原始数组 for bucket in buckets: for num in bucket: arr.append(num) diff --git a/Templates/01.Array/Array-SelectionSort.py b/Templates/01.Array/Array-SelectionSort.py index 63a9df6f..42d0ab71 100644 --- a/Templates/01.Array/Array-SelectionSort.py +++ b/Templates/01.Array/Array-SelectionSort.py @@ -1,12 +1,12 @@ class Solution: def selectionSort(self, arr): for i in range(len(arr) - 1): - # 记录未排序序列中最小数的索引 + # 记录未排序部分中最小值的位置 min_i = i for j in range(i + 1, len(arr)): if arr[j] < arr[min_i]: min_i = j - # 如果找到最小数,将 i 位置上元素与最小数位置上元素进行交换 + # 如果找到最小值的位置,将 i 位置上元素与最小值位置上的元素进行交换 if i != min_i: arr[i], arr[min_i] = arr[min_i], arr[i] return arr diff --git a/Templates/01.Array/Array-ShellSort.py b/Templates/01.Array/Array-ShellSort.py index 815d3d10..3867014e 100644 --- a/Templates/01.Array/Array-ShellSort.py +++ b/Templates/01.Array/Array-ShellSort.py @@ -2,15 +2,21 @@ class Solution: def shellSort(self, arr): size = len(arr) gap = size // 2 - + # 按照 gap 分组 while gap> 0: + # 对每组元素进行插入排序 for i in range(gap, size): + # temp 为每组中无序序列第 1 个元素 temp = arr[i] j = i + # 从右至左遍历每组中的有序序列元素 while j>= gap and arr[j - gap]> temp: + # 将每组有序序列中插入位置右侧的元素依次在组中右移一位 arr[j] = arr[j - gap] j -= gap + # 将该元素插入到适当位置 arr[j] = temp + # 缩小 gap 间隔 gap = gap // 2 return arr

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