diff --git a/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md b/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md index 6e438003..dbd4679f 100644 --- a/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md +++ b/Contents/09.Algorithm-Base/01.Enumeration-Algorithm/01.Enumeration-Algorithm.md @@ -180,7 +180,7 @@ class Solution: ##### 思路 1:枚举算法(超时) -对于小于 $n$ 的每一个数 $x$,我们可以枚举区间 $[2, x - 1]$ 上的数是否是 $x$ 的因数,即是否存在能被 $x$ 整数的数。如果存在,则该数 $x$ 不是质数。如果不存在,则该数 $x$ 是质数。 +对于小于 $n$ 的每一个数 $x$,我们可以枚举区间 $[2, x - 1]$ 上的数是否是 $x$ 的因数,即是否存在能被 $x$ 整除的数。如果存在,则该数 $x$ 不是质数。如果不存在,则该数 $x$ 是质数。 这样我们就可以通过枚举 $[2, n - 1]$ 上的所有数 $x$,并判断 $x$ 是否为质数。 diff --git "a/Solutions/0091. \350\247\243\347\240\201\346\226\271\346\263\225.md" "b/Solutions/0091. \350\247\243\347\240\201\346\226\271\346\263\225.md" index 18045cf5..a168bc10 100644 --- "a/Solutions/0091. \350\247\243\347\240\201\346\226\271\346\263\225.md" +++ "b/Solutions/0091. \350\247\243\347\240\201\346\226\271\346\263\225.md" @@ -5,25 +5,25 @@ ## 题目大意 -**描述**:给定一个数字字符串 `s`。该字符串已经按照下面的映射关系进行了编码: +**描述**:给定一个数字字符串 $s$。该字符串已经按照下面的映射关系进行了编码: -- `A` 映射为 `1`。 -- `B` 映射为 `2`。 +- `A` 映射为 $1$。 +- `B` 映射为 $2$。 - ... -- `Z` 映射为 `26`。 +- `Z` 映射为 $26$。 -基于上述映射的方法,现在对字符串 `s` 进行「解码」。即从数字到字母进行反向映射。比如 `"11106"` 可以映射为: +基于上述映射的方法,现在对字符串 $s$ 进行「解码」。即从数字到字母进行反向映射。比如 `"11106"` 可以映射为: -- `"AAJF"`,将消息分组为 `(1 1 10 6)`。 -- `"KJF"`,将消息分组为 `(11 10 6)`。 +- `"AAJF"`,将消息分组为 $(1 1 10 6)$。 +- `"KJF"`,将消息分组为 $(11 10 6)$。 **要求**:计算出共有多少种可能的解码方案。 **说明**: - $1 \le s.length \le 100$。 -- `s` 只包含数字,并且可能包含前导零。 -- 题目数据保证答案肯定是一个 `32` 位的整数。 +- $s$ 只包含数字,并且可能包含前导零。 +- 题目数据保证答案肯定是一个 $32$ 位的整数。 **示例**: @@ -45,14 +45,14 @@ ###### 2. 定义状态 -定义状态 `dp[i]` 表示为:字符串 `s` 前 `i` 个字符构成的字符串可能构成的翻译方案数。 +定义状态 $dp[i]$ 表示为:字符串 $s$ 前 $i$ 个字符构成的字符串可能构成的翻译方案数。 ###### 3. 状态转移方程 -`dp[i]` 的来源有两种情况: +$dp[i]$ 的来源有两种情况: -1. 使用了一个字符,对 `s[i]` 进行翻译。只要 `s[i] != 0`,就可以被翻译为 `A` ~ `I` 的某个字母,此时方案数为 `dp[i] = dp[i - 1]`。 -2. 使用了两个字符,对 `s[i - 1]` 和 `s[i]` 进行翻译,只有 `s[i - 1] != 0`,且 `s[i - 1]` 和 `s[i]` 组成的整数必须小于等于 `26` 才能翻译,可以翻译为 `J` ~ `Z` 中的某字母,此时方案数为 `dp[i] = dp[i - 2]`。 +1. 使用了一个字符,对 $s[i]$ 进行翻译。只要 $s[i] != 0$,就可以被翻译为 `A` ~ `I` 的某个字母,此时方案数为 $dp[i] = dp[i - 1]$。 +2. 使用了两个字符,对 $s[i - 1]$ 和 $s[i]$ 进行翻译,只有 $s[i - 1] != 0$,且 $s[i - 1]$ 和 $s[i]$ 组成的整数必须小于等于 $26$ 才能翻译,可以翻译为 `J` ~ `Z` 中的某字母,此时方案数为 $dp[i] = dp[i - 2]$。 这两种情况有可能是同时存在的,也有可能都不存在。在进行转移的时候,将符合要求的方案数累加起来即可。 @@ -62,12 +62,12 @@ $dp[i] += \begin{cases} \begin{array} \ dp[i-1] & s[i] \ne 0 \cr dp[i-2] & s[i- ###### 4. 初始条件 -- 字符串为空时,只有一个翻译方案,翻译为空字符串,即 `dp[0] = 1`。 -- 字符串只有一个字符时,需要考虑该字符是否为 `0`,不为 `0` 的话,`dp[1] = 1`,为 `0` 的话,`dp[0] = 0`。 +- 字符串为空时,只有一个翻译方案,翻译为空字符串,即 $dp[0] = 1$。 +- 字符串只有一个字符时,需要考虑该字符是否为 $0$,不为 $0$ 的话,$dp[1] = 1$,为 $0$ 的话,$dp[0] = 0$。 ###### 5. 最终结果 -根据我们之前定义的状态,`dp[i]` 表示为:字符串 `s` 前 `i` 个字符构成的字符串可能构成的翻译方案数。则最终结果为 `dp[size]`,`size` 为字符串长度。 +根据我们之前定义的状态,$dp[i]$ 表示为:字符串 $s$ 前 $i$ 个字符构成的字符串可能构成的翻译方案数。则最终结果为 $dp[size]$,$size$ 为字符串长度。 ### 思路 1:动态规划代码 diff --git "a/Solutions/0095. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221 II.md" "b/Solutions/0095. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221 II.md" index eb13d9d2..c4ed13bb 100644 --- "a/Solutions/0095. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221 II.md" +++ "b/Solutions/0095. \344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221 II.md" @@ -5,22 +5,48 @@ ## 题目大意 -给定一个整数 `n`,请返回以 `1` 到 `n` 为节点构成的「二叉搜索树」,可以按任意顺序返回答案。 +**描述**:给定一个整数 $n$。 + +**要求**:请生成返回以 $1$ 到 $n$ 为节点构成的「二叉搜索树」,可以按任意顺序返回答案。 + +**说明**: + +- $1 \le n \le 8$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2021/01/18/uniquebstn3.jpg) + +```python +输入:n = 3 +输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]] +``` + +- 示例 2: + +```python +输入:n = 1 +输出:[[1]] +``` ## 解题思路 -如果根节点为 `i`,则左子树的节点为 `(1, 2, ..., i - 1)`,右子树的节点为 `(i + 1, i + 2, ..., n)`。可以递归的构建二叉树。 +### 思路 1:递归遍历 + +如果根节点为 $i$,则左子树的节点为 $(1, 2, ..., i - 1)$,右子树的节点为 $(i + 1, i + 2, ..., n)$。可以递归的构建二叉树。 -定义递归函数 `generateTrees(start, end)`,表示生成 `[left, ..., right]` 构成的所有可能的二叉搜索树。 +定义递归函数 `generateTrees(start, end)`,表示生成 $[left, ..., right]$ 构成的所有可能的二叉搜索树。 -- 如果 `start > end`,返回 [None]。 +- 如果 $start > end$,返回 `[None]`。 - 初始化存放所有可能二叉搜索树的数组。 -- 遍历 `[left, ..., right]` 的每一个节点 `i`,将其作为根节点。 +- 遍历 $[left, ..., right]$ 的每一个节点 $i$,将其作为根节点。 - 递归构建左右子树。 - 将所有符合要求的左右子树组合起来,将其加入到存放二叉搜索树的数组中。 - 返回存放二叉搜索树的数组。 -## 代码 +### 思路 1:代码 ```python class Solution: @@ -45,3 +71,8 @@ class Solution: return generateTrees(1, n) ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(C_n)$,其中 $C_n$ 是第 $n$ 个卡特兰数。 +- **空间复杂度**:$O(C_n)$,其中 $C_n$ 是第 $n$ 个卡特兰数。 + diff --git "a/Solutions/0118. \346\235\250\350\276\211\344\270\211\350\247\222.md" "b/Solutions/0118. \346\235\250\350\276\211\344\270\211\350\247\222.md" index 2060990c..e97957d6 100644 --- "a/Solutions/0118. \346\235\250\350\276\211\344\270\211\350\247\222.md" +++ "b/Solutions/0118. \346\235\250\350\276\211\344\270\211\350\247\222.md" @@ -5,9 +5,9 @@ ## 题目大意 -**描述**:给定一个整数 `numRows`。 +**描述**:给定一个整数 $numRows$。 -**要求**:生成前 `numRows` 行的杨辉三角。 +**要求**:生成前 $numRows$ 行的杨辉三角。 **说明**: @@ -30,6 +30,13 @@ ] ``` +- 示例 2: + +```python +输入: numRows = 1 +输出: [[1]] +``` + ## 解题思路 ### 思路 1:动态规划 @@ -40,16 +47,16 @@ ###### 2. 定义状态 -定义状态 `dp[i][j]` 为:杨辉三角第 `i` 行、第 `j` 列位置上的值。 +定义状态 $dp[i][j]$ 为:杨辉三角第 $i$ 行、第 $j$ 列位置上的值。 ###### 3. 状态转移方程 -根据观察,很容易得出状态转移方程为:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`,此时 `i > 0,j > 0`。 +根据观察,很容易得出状态转移方程为:$dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]$,此时 $i > 0$,$j > 0$。 ###### 4. 初始条件 -- 每一行第一列都为 `1`,即 `dp[i][0] = 1`。 -- 每一行最后一列都为 `1`,即 `dp[i][i] = 1`。 +- 每一行第一列都为 $1$,即 $dp[i][0] = 1$。 +- 每一行最后一列都为 $1$,即 $dp[i][i] = 1$。 ###### 5. 最终结果 @@ -83,15 +90,15 @@ class Solution: ### 思路 2:动态规划 + 滚动数组优化 -因为 `dp[i][j]` 仅依赖于上一行(第 `i - 1` 行)的 `dp[i - 1][j - 1]` 和 `dp[i - 1][j]`,所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。 +因为 $dp[i][j]$ 仅依赖于上一行(第 $i - 1$ 行)的 $dp[i - 1][j - 1]$ 和 $dp[i - 1][j]$,所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。 其实我们还可以进一步进行优化,即我们只需要使用一个一维数组保存上一阶段的所有状态。 -定义 `dp[j]` 为杨辉三角第 `i` 行第 `j` 列位置上的值。则第 `i + 1` 行、第 `j` 列的值可以通过 `dp[j]` + `dp[j - 1]` 所得到。 +定义 $dp[j]$ 为杨辉三角第 $i$ 行第 $j$ 列位置上的值。则第 $i + 1$ 行、第 $j$ 列的值可以通过 $dp[j]$ + $dp[j - 1]$ 所得到。 这样我们就可以对这个一维数组保存的「上一阶段的所有状态值」进行逐一计算,从而获取「当前阶段的所有状态值」。 -需要注意:本题在计算的时候需要从右向左依次遍历每个元素位置,这是因为如果从左向右遍历,如果当前元素 `dp[j]` 已经更新为当前阶段第 `j` 列位置的状态值之后,右侧 `dp[j + 1]` 想要更新的话,需要的是上一阶段的状态值 `dp[j]`,而此时 `dp[j]` 已经更新了,会破坏当前阶段的状态值。而如果用从右向左的顺序,则不会出现该问题。 +需要注意:本题在计算的时候需要从右向左依次遍历每个元素位置,这是因为如果从左向右遍历,如果当前元素 $dp[j]$ 已经更新为当前阶段第 $j$ 列位置的状态值之后,右侧 $dp[j + 1]$ 想要更新的话,需要的是上一阶段的状态值 $dp[j]$,而此时 $dp[j]$ 已经更新了,会破坏当前阶段的状态值。而如果用从右向左的顺序,则不会出现该问题。 ### 思路 2:动态规划 + 滚动数组优化代码 diff --git "a/Solutions/0119. \346\235\250\350\276\211\344\270\211\350\247\222 II.md" "b/Solutions/0119. \346\235\250\350\276\211\344\270\211\350\247\222 II.md" index 91b41a60..d4822053 100644 --- "a/Solutions/0119. \346\235\250\350\276\211\344\270\211\350\247\222 II.md" +++ "b/Solutions/0119. \346\235\250\350\276\211\344\270\211\350\247\222 II.md" @@ -5,9 +5,9 @@ ## 题目大意 -**描述**:给定一个非负整数 `rowIndex`。 +**描述**:给定一个非负整数 $rowIndex$。 -**要求**:返回杨辉三角的第 `rowIndex` 行。 +**要求**:返回杨辉三角的第 $rowIndex$ 行。 **说明**: @@ -27,7 +27,7 @@ ### 思路 1:动态规划 -因为这道题是从 `0` 行开始计算,则可以先将 `rowIndex` 加 `1`,计算出总共的行数,即 `numRows = rowIndex + 1`。 +因为这道题是从 $0$ 行开始计算,则可以先将 $rowIndex$ 加 $1$,计算出总共的行数,即 $numRows = rowIndex + 1$。 ###### 1. 划分阶段 @@ -35,20 +35,20 @@ ###### 2. 定义状态 -定义状态 `dp[i][j]` 为:杨辉三角第 `i` 行、第 `j` 列位置上的值。 +定义状态 $dp[i][j]$ 为:杨辉三角第 $i$ 行、第 $j$ 列位置上的值。 ###### 3. 状态转移方程 -根据观察,很容易得出状态转移方程为:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`,此时 `i > 0,j > 0`。 +根据观察,很容易得出状态转移方程为:$dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]$,此时 $i > 0$,$j > 0$。 ###### 4. 初始条件 -- 每一行第一列都为 `1`,即 `dp[i][0] = 1`。 -- 每一行最后一列都为 `1`,即 `dp[i][i] = 1`。 +- 每一行第一列都为 $1$,即 $dp[i][0] = 1$。 +- 每一行最后一列都为 $1$,即 $dp[i][i] = 1$。 ###### 5. 最终结果 -根据题意和状态定义,将 `dp` 最后一行返回。 +根据题意和状态定义,将 $dp$ 最后一行返回。 ### 思路 1:代码 @@ -79,15 +79,15 @@ class Solution: ### 思路 2:动态规划 + 滚动数组优化 -因为 `dp[i][j]` 仅依赖于上一行(第 `i - 1` 行)的 `dp[i - 1][j - 1]` 和 `dp[i - 1][j]`,所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。 +因为 $dp[i][j]$ 仅依赖于上一行(第 $i - 1$ 行)的 $dp[i - 1][j - 1]$ 和 $dp[i - 1][j]$,所以我们没必要保存所有阶段的状态,只需要保存上一阶段的所有状态和当前阶段的所有状态就可以了,这样使用两个一维数组分别保存相邻两个阶段的所有状态就可以实现了。 其实我们还可以进一步进行优化,即我们只需要使用一个一维数组保存上一阶段的所有状态。 -定义 `dp[j]` 为杨辉三角第 `i` 行第 `j` 列位置上的值。则第 `i + 1` 行、第 `j` 列的值可以通过 `dp[j]` + `dp[j - 1]` 所得到。 +定义 $dp[j]$ 为杨辉三角第 $i$ 行第 $j$ 列位置上的值。则第 $i + 1$ 行、第 $j$ 列的值可以通过 $dp[j]$ + $dp[j - 1]$ 所得到。 这样我们就可以对这个一维数组保存的「上一阶段的所有状态值」进行逐一计算,从而获取「当前阶段的所有状态值」。 -需要注意:本题在计算的时候需要从右向左依次遍历每个元素位置,这是因为如果从左向右遍历,如果当前元素 `dp[j]` 已经更新为当前阶段第 `j` 列位置的状态值之后,右侧 `dp[j + 1]` 想要更新的话,需要的是上一阶段的状态值 `dp[j]`,而此时 `dp[j]` 已经更新了,会破坏当前阶段的状态值。而是用从左向左的顺序,则不会出现该问题。 +需要注意:本题在计算的时候需要从右向左依次遍历每个元素位置,这是因为如果从左向右遍历,如果当前元素 $dp[j]$ 已经更新为当前阶段第 $j$ 列位置的状态值之后,右侧 $dp[j + 1]$ 想要更新的话,需要的是上一阶段的状态值 $dp[j]$,而此时 $dp[j]$ 已经更新了,会破坏当前阶段的状态值。而是用从左向左的顺序,则不会出现该问题。 ### 思路 2:动态规划 + 滚动数组优化代码 diff --git "a/Solutions/0120. \344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" "b/Solutions/0120. \344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" index 22e28c5a..47a6ea0d 100644 --- "a/Solutions/0120. \344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" +++ "b/Solutions/0120. \344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.md" @@ -5,9 +5,9 @@ ## 题目大意 -**描述**:给定一个代表三角形的二维数组 `triangle`,`triangle` 共有 `n` 行,其中第 `i` 行(从 `0` 开始编号)包含了 `i + 1` 个数。 +**描述**:给定一个代表三角形的二维数组 $triangle$,$triangle$ 共有 $n$ 行,其中第 $i$ 行(从 $0$ 开始编号)包含了 $i + 1$ 个数。 -我们每一步只能从当前位置移动到下一行中相邻的节点上。也就是说,如果正位于第 `i` 行第 `j` 列的节点,那么下一步可以移动到第 `i + 1` 行第 `j` 列的位置上,或者第 `i + 1` 行,第 `j + 1` 列的位置上。 +我们每一步只能从当前位置移动到下一行中相邻的节点上。也就是说,如果正位于第 $i$ 行第 $j$ 列的节点,那么下一步可以移动到第 $i + 1$ 行第 $j$ 列的位置上,或者第 $i + 1$ 行,第 $j + 1$ 列的位置上。 **要求**:找出自顶向下的最小路径和。 @@ -33,6 +33,13 @@ 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 ``` +- 示例 2: + +```python +输入:triangle = [[-10]] +输出:-10 +``` + ## 解题思路 ### 思路 1:动态规划 @@ -43,21 +50,21 @@ ###### 2. 定义状态 -定义状态 `dp[i][j]` 表示为:从顶部走到第 `i` 行(从 `0` 开始编号)、第 `j` 列的位置时的最小路径和。 +定义状态 $dp[i][j]$ 表示为:从顶部走到第 $i$ 行(从 $0$ 开始编号)、第 $j$ 列的位置时的最小路径和。 ###### 3. 状态转移方程 -由于每一步只能从当前位置移动到下一行中相邻的节点上,想要移动到第 `i` 行、第 `j` 列的位置,那么上一步只能在第 `i - 1` 行、第 `j - 1` 列的位置上,或者在第 `i - 1` 行、第 `j` 列的位置上。则状态转移方程为: +由于每一步只能从当前位置移动到下一行中相邻的节点上,想要移动到第 $i$ 行、第 $j$ 列的位置,那么上一步只能在第 $i - 1$ 行、第 $j - 1$ 列的位置上,或者在第 $i - 1$ 行、第 $j$ 列的位置上。则状态转移方程为: -`dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]`。其中 `triangle[i][j]` 表示第 `i` 行、第 `j` 列位置上的元素值。 +$dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]$。其中 $triangle[i][j]$ 表示第 $i$ 行、第 $j$ 列位置上的元素值。 ###### 4. 初始条件 - 在第 `0` 行、第 `j` 列时,最小路径和为 `triangle[0][0]`,即 `dp[0][0] = triangle[0][0]`。 + 在第 $0$ 行、第 $j$ 列时,最小路径和为 $triangle[0][0]$,即 $dp[0][0] = triangle[0][0]$。 ###### 5. 最终结果 -根据我们之前定义的状态,`dp[i][j]` 表示为:从顶部走到第 `i` 行(从 `0` 开始编号)、第 `j` 列的位置时的最小路径和。为了计算出最小路径和,则需要再遍历一遍 `dp[size - 1]` 行的每一列,求出最小值即为最终结果。 +根据我们之前定义的状态,$dp[i][j]$ 表示为:从顶部走到第 $i$ 行(从 $0$ 开始编号)、第 $j$ 列的位置时的最小路径和。为了计算出最小路径和,则需要再遍历一遍 $dp[size - 1]$ 行的每一列,求出最小值即为最终结果。 ### 思路 1:动态规划代码 diff --git "a/Solutions/0241. \344\270\272\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217\350\256\276\350\256\241\344\274\230\345\205\210\347\272\247.md" "b/Solutions/0241. \344\270\272\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217\350\256\276\350\256\241\344\274\230\345\205\210\347\272\247.md" index e2ee372b..63ccb2a2 100644 --- "a/Solutions/0241. \344\270\272\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217\350\256\276\350\256\241\344\274\230\345\205\210\347\272\247.md" +++ "b/Solutions/0241. \344\270\272\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217\350\256\276\350\256\241\344\274\230\345\205\210\347\272\247.md" @@ -79,5 +79,5 @@ class Solution: ### 思路 1:复杂度分析 -- **时间复杂度**:$O(C_n)$,其中 $n$ 为结果数组的大小,$C_n$ 是第 $k$ 个卡特兰数。 +- **时间复杂度**:$O(C_n)$,其中 $n$ 为结果数组的大小,$C_n$ 是第 $n$ 个卡特兰数。 - **空间复杂度**:$O(C_n)$。 diff --git "a/Solutions/0354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230.md" "b/Solutions/0354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230.md" index 93907937..ae47e707 100644 --- "a/Solutions/0354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230.md" +++ "b/Solutions/0354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230.md" @@ -5,7 +5,7 @@ ## 题目大意 -给定一个二维整数数组 envelopes 表示信封,其中 `envelopes[i] = [wi, hi]`,表示第 i 个信封的宽度 wi 和高度 hi。 +给定一个二维整数数组 envelopes 表示信封,其中 $envelopes[i] = [wi, hi]$,表示第 $i$ 个信封的宽度 $w_i$ 和高度 $h_i$。 当一个信封的宽度和高度比另一个信封大时,则小的信封可以放进大信封里,就像俄罗斯套娃一样。 diff --git "a/Solutions/0403. \351\235\222\350\233\231\350\277\207\346\262\263.md" "b/Solutions/0403. \351\235\222\350\233\231\350\277\207\346\262\263.md" index 0788463e..ad1102eb 100644 --- "a/Solutions/0403. \351\235\222\350\233\231\350\277\207\346\262\263.md" +++ "b/Solutions/0403. \351\235\222\350\233\231\350\277\207\346\262\263.md" @@ -7,11 +7,11 @@ **描述**:一只青蛙要过河,这条河被等分为若干个单元格,每一个单元格内可能放油一块石子(也可能没有)。青蛙只能跳到有石子的单元格内,不能跳到没有石子的单元格内。 -现在给定一个严格按照升序排序的数组 `stones`,其中 `stones[i]` 代表第 `i` 块石子所在的单元格序号。默认第 `0` 块石子序号为 `0`(即 `stones[0] == 0`)。 +现在给定一个严格按照升序排序的数组 $stones$,其中 $stones[i]$ 代表第 $i$ 块石子所在的单元格序号。默认第 $0$ 块石子序号为 $0$(即 $stones[0] == 0$)。 -开始时,青蛙默认站在序号为 `0` 石子上(即 `stones[0]`),并且假定它第 `1` 步只能跳跃 `1` 个单位(即只能从序号为 `0` 的单元格跳到序号为 `1` 的单元格)。 +开始时,青蛙默认站在序号为 $0$ 石子上(即 $stones[0]$),并且假定它第 $1$ 步只能跳跃 $1$ 个单位(即只能从序号为 $0$ 的单元格跳到序号为 $1$ 的单元格)。 -如果青蛙在上一步向前跳跃了 `k` 个单位,则下一步只能向前跳跃 `k - 1`、`k` 或者 `k + 1` 个单位。 +如果青蛙在上一步向前跳跃了 $k$ 个单位,则下一步只能向前跳跃 $k - 1$、$k$ 或者 $k + 1$ 个单位。 **要求**:判断青蛙能否成功过河(即能否在最后一步跳到最后一块石子上)。如果能,则返回 `True`;否则,则返回 `False`。 @@ -36,11 +36,11 @@ ### 思路 1:动态规划 -题目中说:如果青蛙在上一步向前跳跃了 `k` 个单位,则下一步只能向前跳跃 `k - 1`、`k` 或者 `k + 1` 个单位。则下一步的状态可以由 `3` 种状态转移而来。 +题目中说:如果青蛙在上一步向前跳跃了 $k$ 个单位,则下一步只能向前跳跃 $k - 1$、$k$ 或者 $k + 1$ 个单位。则下一步的状态可以由 $3$ 种状态转移而来。 -- 上一步所在石子到下一步所在石头的距离为 `k - 1`。 -- 上一步所在石子到下一步所在石头的距离为 `k`。 -- 上一步所在石子到下一步所在石头的距离为 `k + 1`。 +- 上一步所在石子到下一步所在石头的距离为 $k - 1$。 +- 上一步所在石子到下一步所在石头的距离为 $k$。 +- 上一步所在石子到下一步所在石头的距离为 $k + 1$。 则我们可以通过石子块数,跳跃距离来进行阶段划分和定义状态,以及推导状态转移方程。 @@ -50,22 +50,22 @@ ###### 2. 定义状态 -定义状态 `dp[i][k]` 表示为:青蛙能否以长度为 `k` 的距离,到达第 `i` 块石子。 +定义状态 $dp[i][k]$ 表示为:青蛙能否以长度为 $k$ 的距离,到达第 $i$ 块石子。 ###### 3. 状态转移方程 -1. 外层循环遍历每一块石子 `i`,对于每一块石子 `i`,使用内层循环遍历石子 `i` 之前所有的石子 `j`。 -2. 并计算出上一步所在石子 `j` 到当前所在石子 `i` 之间的距离为 `k`。 -3. 如果上一步所在石子 `j` 通过上上一步以长度为 `k - 1`、`k` 或者 `k + 1` 的距离到达石子 `j`,那么当前步所在石子也可以通过 `k` 的距离到达石子 `i`。即通过检查 `dp[j][k - 1]`、`dp[j][k]`、`dp[j][k + 1]` 中是否至少有一个为真,即可判断 `dp[i][k]` 是否为真。 - - 即:`dp[i][k] = dp[j][k - 1] or dp[j][k] or dp[j][k + 1] `。 +1. 外层循环遍历每一块石子 $i$,对于每一块石子 $i$,使用内层循环遍历石子 $i$ 之前所有的石子 $j$。 +2. 并计算出上一步所在石子 $j$ 到当前所在石子 $i$ 之间的距离为 $k$。 +3. 如果上一步所在石子 $j$ 通过上上一步以长度为 $k - 1$、$k$ 或者 $k + 1$ 的距离到达石子 $j$,那么当前步所在石子也可以通过 $k$ 的距离到达石子 $i$。即通过检查 $dp[j][k - 1]$、$dp[j][k]$、$dp[j][k + 1]$ 中是否至少有一个为真,即可判断 $dp[i][k]$ 是否为真。 + - 即:$dp[i][k] = dp[j][k - 1] \text{ or } dp[j][k] or dp[j][k + 1] $。 ###### 4. 初始条件 -刚开始青蛙站在序号为 `0` 石子上(即 `stones[0]`),肯定能以长度为 `0` 的距离,到达第 `0` 块石子,即 `dp[0][0] = True`。 +刚开始青蛙站在序号为 $0$ 石子上(即 $stones[0]$),肯定能以长度为 $0$ 的距离,到达第 $0$ 块石子,即 $dp[0][0] = True$。 ###### 5. 最终结果 -根据我们之前定义的状态,`dp[i][k]` 表示为:青蛙能否以长度为 `k` 的距离,到达第 `i` 块石子。则如果 `dp[size - 1][k]` 为真,则说明青蛙能成功过河(即能在最后一步跳到最后一块石子上);否则则说明青蛙不能成功过河。 +根据我们之前定义的状态,$dp[i][k]$ 表示为:青蛙能否以长度为 $k$ 的距离,到达第 $i$ 块石子。则如果 $dp[size - 1][k]$ 为真,则说明青蛙能成功过河(即能在最后一步跳到最后一块石子上);否则则说明青蛙不能成功过河。 ### 思路 1:动态规划代码 diff --git "a/Solutions/0560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Solutions/0560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" index 128a0cc8..a63454c6 100644 --- "a/Solutions/0560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" +++ "b/Solutions/0560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" @@ -5,13 +5,35 @@ ## 题目大意 -给定一个整数数组 `nums` 和一个整数 `k`。 +**描述**:给定一个整数数组 $nums$ 和一个整数 $k$。 -要求:找到该数组中和为 `k` 的连续子数组的个数。 +**要求**:找到该数组中和为 $k$ 的连续子数组的个数。 + +**说明**: + +- $1 \le nums.length \le 2 \times 10^4$。 +- $-1000 \le nums[i] \le 1000$。 + $-10^7 \le k \le 10^7$。 + +**示例**: + +- 示例 1: + +```python +输入:nums = [1,1,1], k = 2 +输出:2 +``` + +- 示例 2: + +```python +输入:nums = [1,2,3], k = 3 +输出:2 +``` ## 解题思路 -看到题目的第一想法是通过滑动窗口求解。但是做下来发现有些数据样例无法通过。发现这道题目中的整数不能保证都为正数,则无法通过滑动窗口进行求解。 +### 思路 1:枚举算法(超时) 先考虑暴力做法,外层两重循环,遍历所有连续子数组,然后最内层再计算一下子数组的和。部分代码如下: @@ -23,26 +45,53 @@ for i in range(len(nums)): 这样下来时间复杂度就是 $O(n^3)$ 了。下一步是想办法降低时间复杂度。 -先用一重循环遍历数组,计算出数组 `nums` 中前 i 个元素的和(前缀和),保存到一维数组 `pre_sum` 中,那么对于任意 `[j..i]` 的子数组 的和为 `pre_sum[i] - pre_sum[j - 1]`。这样计算子数组和的时间复杂度降为了 $O(1)$。总体时间复杂度为 $O(n^2)$。 +对于以 $i$ 开头,以 $j$ 结尾($i \le j$)的子数组 $nums[i]…nums[j]$ 来说,我们可以通过顺序遍历 $j$,逆序遍历 $i$ 的方式(或者前缀和的方式),从而在 $O(n^2)$ 的时间复杂度内计算出子数组的和,同时使用变量 $cnt$ 统计出和为 $k$ 的子数组个数。 + +但这样提交上去超时了。 + +### 思路 1:代码 + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt = 0 + for j in range(len(nums)): + sum = 0 + for i in range(j, -1, -1): + sum += nums[i] + if sum == k: + cnt += 1 + + return cnt +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:前缀和 + 哈希表 + +先用一重循环遍历数组,计算出数组 $nums$ 中前 $j$ 个元素的和(前缀和),保存到一维数组 $pre\underline{}sum$ 中,那么对于任意 $nums[i]…nums[j]$ 的子数组的和为 $pre\underline{}sum[j] - pre\underline{}sum[i - 1]$。这样计算子数组和的时间复杂度降为了 $O(1)$。总体时间复杂度为 $O(n^2)$。 但是还是超时了。。 -由于我们只关心和为 `k` 出现的次数,不关心具体的解,可以使用哈希表来加速运算。 +由于我们只关心和为 $k$ 出现的次数,不关心具体的解,可以使用哈希表来加速运算。 -`pre_sum[i]` 的定义是前 `i` 个元素和,则 `pre_sum[i]` 可以由 `pre_sum[i - 1]` 递推而来,即:`pre_sum[i] = pre_sum[i - 1] + num[i]`。 `[j..i]` 子数组和为 `k` 可以转换为:`pre_sum[i] - pre_sum[j - 1] == k`。 +$pre\underline{}sum[i]$ 的定义是前 $i$ 个元素和,则 $pre\underline{}sum[i]$ 可以由 $pre\underline{}sum[i - 1]$ 递推而来,即:$pre\underline{}sum[i] = pre\underline{}sum[i - 1] + num[i]$。 $[i..j]$ 子数组和为 $k$ 可以转换为:$pre\underline{}sum[j] - pre\underline{}sum[i - 1] == k$。 -综合一下,可得:`pre_sum[j - 1] == pre_sum[i] - k `。 +综合一下,可得:$pre\underline{}sum[i - 1] == pre\underline{}sum[j] - k $。 -所以,当我们考虑以 `i` 结尾和为 `k` 的连续子数组个数时,只需要统计有多少个前缀和为 `pre_sum[i] - k` (即 `pre_sum[j - 1]`)的个数即可。具体做法如下: +所以,当我们考虑以 $j$ 结尾和为 $k$ 的连续子数组个数时,只需要统计有多少个前缀和为 $pre\underline{}sum[j] - k$ (即 $pre\underline{}sum[i - 1]$)的个数即可。具体做法如下: -- 使用 `pre_sum` 变量记录前缀和(代表 `pre_sum[i]`)。 -- 使用哈希表 `pre_dic` 记录 `pre_sum[i]` 出现的次数。键值对为 `pre_sum[i] : pre_sum_count`。 -- 从左到右遍历数组,计算当前前缀和 `pre_sum`。 -- 如果 `pre_sum - k` 在哈希表中,则答案个数累加上 `pre_dic[pre_sum - k]`。 -- 如果 `pre_sum` 在哈希表中,则前缀和个数累加 1,即 `pre_dic[pre_sum] += 1`。 +- 使用 $pre\underline{}sum$ 变量记录前缀和(代表 $pre\underline{}sum[j]$)。 +- 使用哈希表 $pre\underline{}dic$ 记录 $pre\underline{}sum[j]$ 出现的次数。键值对为 $pre\underline{}sum[j] : pre\underline{}sum\underline{}count$。 +- 从左到右遍历数组,计算当前前缀和 $pre\underline{}sum$。 +- 如果 $pre\underline{}sum - k$ 在哈希表中,则答案个数累加上 $pre\underline{}dic[pre\underline{}sum - k]$。 +- 如果 $pre\underline{}sum$ 在哈希表中,则前缀和个数累加 $1$,即 $pre\underline{}dic[pre\underline{}sum] += 1$。 - 最后输出答案个数。 -## 代码 +### 思路 2:代码 ```python class Solution: @@ -61,3 +110,8 @@ class Solution: return count ``` +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + diff --git "a/Solutions/0639. \350\247\243\347\240\201\346\226\271\346\263\225 II.md" "b/Solutions/0639. \350\247\243\347\240\201\346\226\271\346\263\225 II.md" index 7ebd61e3..8d1500c2 100644 --- "a/Solutions/0639. \350\247\243\347\240\201\346\226\271\346\263\225 II.md" +++ "b/Solutions/0639. \350\247\243\347\240\201\346\226\271\346\263\225 II.md" @@ -5,27 +5,27 @@ ## 题目大意 -**描述**:给定一个包含数字和字符 `'*'` 的字符串 `s`。该字符串已经按照下面的映射关系进行了编码: +**描述**:给定一个包含数字和字符 `'*'` 的字符串 $s$。该字符串已经按照下面的映射关系进行了编码: -- `A` 映射为 `1`。 -- `B` 映射为 `2`。 +- `A` 映射为 $1$。 +- `B` 映射为 $2$。 - ... -- `Z` 映射为 `26`。 +- `Z` 映射为 $26$。 -除了上述映射方法,字符串 `s` 中可能包含字符 `'*'`,可以表示 `1` ~ `9` 的任一数字(不包括 `0`)。例如字符串 `"1*"` 可以表示为 `"11"`、`"12"`、…、`"18"`、`"19"` 中的任何一个编码。 +除了上述映射方法,字符串 $s$ 中可能包含字符 `'*'`,可以表示 $1$ ~ $9$ 的任一数字(不包括 $0$)。例如字符串 `"1*"` 可以表示为 `"11"`、`"12"`、…、`"18"`、`"19"` 中的任何一个编码。 基于上述映射的方法,现在对字符串 `s` 进行「解码」。即从数字到字母进行反向映射。比如 `"11106"` 可以映射为: -- `"AAJF"`,将消息分组为 `(1 1 10 6)`。 -- `"KJF"`,将消息分组为 `(11 10 6)`。 +- `"AAJF"`,将消息分组为 $(1 1 10 6)$。 +- `"KJF"`,将消息分组为 $(11 10 6)$。 **要求**:计算出共有多少种可能的解码方案。 **说明**: - $1 \le s.length \le 100$。 -- `s` 只包含数字,并且可能包含前导零。 -- 题目数据保证答案肯定是一个 `32` 位的整数。 +- $s$ 只包含数字,并且可能包含前导零。 +- 题目数据保证答案肯定是一个 $32$ 位的整数。 ```python 输入:s = "*" @@ -45,13 +45,13 @@ ###### 2. 定义状态 -定义状态 `dp[i]` 表示为:字符串 `s` 前 `i` 个字符构成的字符串可能构成的翻译方案数。 +定义状态 $dp[i]$ 表示为:字符串 $s$ 前 $i$ 个字符构成的字符串可能构成的翻译方案数。 ###### 3. 状态转移方程 -`dp[i]` 的来源有两种情况: +$dp[i]$ 的来源有两种情况: -1. 使用了一个字符,对 `s[i]` 进行翻译: +1. 使用了一个字符,对 $s[i]$ 进行翻译: 1. 如果 `s[i] == '*'`,则 `s[i]` 可以视作区间 `[1, 9]` 上的任意一个数字,可以被翻译为 `A` ~ `I`。此时当前位置上的方案数为 `9`,即 `dp[i] = dp[i - 1] * 9`。 2. 如果 `s[i] == '0'`,则无法被翻译,此时当前位置上的方案数为 `0`,即 `dp[i] = dp[i - 1] * 0`。 3. 如果是其他情况(即 `s[i]` 是区间 `[1, 9]` 上某一个数字),可以被翻译为 `A` ~ `I` 对应位置上的某个字母。此时当前位置上的方案数为 `1`,即 `dp[i] = dp[i - 1] * 1`。 diff --git "a/Solutions/0800. \347\233\270\344\274\274 RGB \351\242\234\350\211\262.md" "b/Solutions/0800. \347\233\270\344\274\274 RGB \351\242\234\350\211\262.md" index a636822a..eacb4b25 100644 --- "a/Solutions/0800. \347\233\270\344\274\274 RGB \351\242\234\350\211\262.md" +++ "b/Solutions/0800. \347\233\270\344\274\274 RGB \351\242\234\350\211\262.md" @@ -27,12 +27,12 @@ ### 思路 1:枚举算法 -所有可以简写的颜色范围是 `"#000"` ~ `"#fff"`,共 $16^3 = 4096$ 种颜色。因此,我们可以枚举这些可以简写的颜色,并计算出其与 `color`的相似度,从而找出与 `color` 最相似的颜色。具体做法如下: +所有可以简写的颜色范围是 `"#000"` ~ `"#fff"`,共 $16^3 = 4096$ 种颜色。因此,我们可以枚举这些可以简写的颜色,并计算出其与 $color$的相似度,从而找出与 $color$ 最相似的颜色。具体做法如下: -- 将 `color` 转换为十六进制数,即 `hex_color = int(color[1:], 16)`。 -- 三重循环遍历 `R`、`G`、`B` 三个通道颜色,每一重循环范围为 `0` ~ `15`。 -- 计算出每一种可以简写的颜色对应的十六进制,即 `17 * R * (1 << 16) + 17 * G * (1 << 8) + 17 * B`,`17` 是 `0x11 = 16 + 1 = 17`,`(1 << 16)` 为 `R` 左移的位数,`17 * R * (1 << 16)` 就表示 `R` 通道上对应的十六进制数。`(1 << 8)` 为 `G` 左移的位数,`17 * G * (1 << 8)` 就表示 `G` 通道上对应的十六进制数。`17 * B` 就表示 `B` 通道上对应的十六进制数。 -- 然后我们根据 `color` 的十六进制数,与每一个可以简写的颜色对应的十六进制数,计算出相似度,并找出大相似对应的颜色。将其转换为字符串,并输出。 +- 将 $color$ 转换为十六进制数,即 `hex_color = int(color[1:], 16)`。 +- 三重循环遍历 $R$、$G$、$B$ 三个通道颜色,每一重循环范围为 $0 \sim 15$。 +- 计算出每一种可以简写的颜色对应的十六进制,即 $17 \times R \times (1 << 16) + 17 \times G \times (1 << 8) + 17 \times B$,$17$ 是 $0x11 = 16 + 1 = 17$,$(1 << 16)$ 为 $R$ 左移的位数,$17 \times R \times (1 << 16)$ 就表示 $R$ 通道上对应的十六进制数。$(1 << 8)$ 为 $G$ 左移的位数,$17 \times G \times (1 << 8)$ 就表示 $G$ 通道上对应的十六进制数。$17 \times B$ 就表示 $B$ 通道上对应的十六进制数。 +- 然后我们根据 $color$ 的十六进制数,与每一个可以简写的颜色对应的十六进制数,计算出相似度,并找出大相似对应的颜色。将其转换为字符串,并输出。 ### 思路 1:枚举算法代码 @@ -55,3 +55,8 @@ class Solution: return "#{:06x}".format(ans) ``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(16^3)$。 +- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1994. \345\245\275\345\255\220\351\233\206\347\232\204\346\225\260\347\233\256.md" "b/Solutions/1994. \345\245\275\345\255\220\351\233\206\347\232\204\346\225\260\347\233\256.md" index b0dbda91..650043dc 100644 --- "a/Solutions/1994. \345\245\275\345\255\220\351\233\206\347\232\204\346\225\260\347\233\256.md" +++ "b/Solutions/1994. \345\245\275\345\255\220\351\233\206\347\232\204\346\225\260\347\233\256.md" @@ -14,9 +14,9 @@ - **子集**:通过删除 $nums$ 中一些(可能一个都不删除,也可能全部都删除)元素后剩余元素组成的数组。如果两个子集删除的下标不同,那么它们被视为不同的子集。 - **好子集**:如果 $nums$ 的一个子集中,所有元素的乘积可以表示为一个或多个互不相同的质数的乘积,那么我们称它为好子集。 - - 比如,如果 `nums = [1, 2, 3, 4]`: - - `[2, 3]` ,`[1, 2, 3]` 和 `[1, 3]` 是好子集,乘积分别为 `6 = 2*3` ,`6 = 2*3` 和 `3 = 3` 。 - - `[1, 4]` 和 `[4]` 不是好子集,因为乘积分别为 `4 = 2*2` 和 `4 = 2*2` 。 + - 比如,如果 $nums = [1, 2, 3, 4]$: + - $[2, 3]$,$[1, 2, 3]$ 和 $[1, 3]$ 是好子集,乘积分别为 $6 = 2 \times 3$ ,$6 = 2 \times 3$ 和 $3 = 3$。 + - $[1, 4]$ 和 $[4]$ 不是好子集,因为乘积分别为 $4 = 2 \times 2$ 和 $4 = 2 \times 2$。 - $1 \le nums.length \le 10^5$。 - $1 \le nums[i] \le 30$。 diff --git "a/Solutions/\345\211\221\346\214\207 Offer 62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.md" "b/Solutions/\345\211\221\346\214\207 Offer 62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.md" index b52cf489..003d1f37 100644 --- "a/Solutions/\345\211\221\346\214\207 Offer 62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.md" +++ "b/Solutions/\345\211\221\346\214\207 Offer 62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.md" @@ -5,41 +5,64 @@ ## 题目大意 -`0`、`1`、…、`n - 1` 这 `n` 个数字排成一个圆圈,从数字 `0` 开始,每次从圆圈里删除第 `m` 个数字。现在给定整数 `n` 和 `m`。 +**描述**:$0$、$1$、…、$n - 1$ 这 $n$ 个数字排成一个圆圈,从数字 $0$ 开始,每次从圆圈里删除第 $m$ 个数字。现在给定整数 $n$ 和 $m$。 -要求:求出这个圆圈中剩下的最后一个数字。 +**要求**:求出这个圆圈中剩下的最后一个数字。 + +**说明**: + +- $1 \le num \le 10^5$。 +- $1 \le target \le 10^6$。 + +**示例**: + +- 示例 1: + +```python +输入:num = 7, target = 4 +输出:1 +``` + +- 示例 2: + +```python +输入:num = 12, target = 5 +输出:0 +``` ## 解题思路 -模拟循环删除,需要进行 `n - 1` 轮,每轮需要对节点进行 `m` 次访问操作。总体时间复杂度为 `O(nm)`。 +### 思路 1:枚举 + 模拟 -可以通过找规律来做,以 `n = 5`、`m = 3` 为例。 +模拟循环删除,需要进行 $n - 1$ 轮,每轮需要对节点进行 $m$ 次访问操作。总体时间复杂度为 $O(n \times m)$。 -- 刚开始为 `0`、`1`、`2`、`3`、`4`。 -- 第一次从 `0` 开始数,数 `3` 个数,于是 `2` 出圈,变为 `3`、`4`、`0`、`1`。 -- 第二次从 `3` 开始数,数 `3` 个数,于是 `0` 出圈,变为 `1`、`3`、`4`。 -- 第三次从 `1` 开始数,数 `3` 个数,于是 `4` 出圈,变为 `1`、`3`。 -- 第四次从 `1` 开始数,数 `3` 个数,于是 `1` 出圈,变为 `3`。 -- 所以最终为 `3`。 +可以通过找规律来做,以 $n = 5$、$m = 3$ 为例。 -通过上面的流程可以发现:每隔 `m` 个数就要删除一个数,那么被删除的这个数的下一个数就会成为新的起点。就相当于数组进行左移了 `m` 位。反过来思考的话,从最后一步向前推,则每一步都向右移动了 `m` 位(包括胜利者)。 +- 刚开始为 $0$、$1$、$2$、$3$、$4$。 +- 第一次从 $0$ 开始数,数 $3$ 个数,于是 $2$ 出圈,变为 $3$、$4$、$0$、$1$。 +- 第二次从 $3$ 开始数,数 $3$ 个数,于是 $0$ 出圈,变为 $1$、$3$、$4$。 +- 第三次从 $1$ 开始数,数 $3$ 个数,于是 $4$ 出圈,变为 $1$、$3$。 +- 第四次从 $1$ 开始数,数 $3$ 个数,于是 $1$ 出圈,变为 $3$。 +- 所以最终为 $3$。 -如果用 `f(n, m)` 表示: `n` 个数构成环没删除 `m` 个数后,最终胜利者的位置,则 `f(n, m) = f(n - 1, m) + m`。 +通过上面的流程可以发现:每隔 $m$ 个数就要删除一个数,那么被删除的这个数的下一个数就会成为新的起点。就相当于数组进行左移了 $m$ 位。反过来思考的话,从最后一步向前推,则每一步都向右移动了 $m$ 位(包括胜利者)。 -即等于 `n - 1` 个数构成的环没删除 `m` 个数后最终胜利者的位置,像右移动 `m` 次。 +如果用 $f(n, m)$ 表示: $n$ 个数构成环没删除 $m$ 个数后,最终胜利者的位置,则 $f(n, m) = f(n - 1, m) + m$。 -问题是现在并不是真的进行了右移,因为当前数组右移后超过数组容量的部分应该重新放到数组头部位置。所以公式应为:`f(n, m) = [f(n - 1, m) + m] % n`,`n` 为反过来向前推的时候,每一步剩余的数字个数(比如第二步推回第一步,n `4`),则反过来递推公式为: +即等于 $n - 1$ 个数构成的环没删除 $m$ 个数后最终胜利者的位置,像右移动 $m$ 次。 -- `f(1, m) = 0`。 -- `f(2, m) = [f(1, m) + m] % 2`。 -- `f(3, m) = [f(2, m) + m] % 3`。 +问题是现在并不是真的进行了右移,因为当前数组右移后超过数组容量的部分应该重新放到数组头部位置。所以公式应为:$f(n, m) = [f(n - 1, m) + m] % n$,$n$ 为反过来向前推的时候,每一步剩余的数字个数(比如第二步推回第一步,n $4$),则反过来递推公式为: + +- $f(1, m) = 0$。 +- $f(2, m) = [f(1, m) + m] % 2$。 +- $f(3, m) = [f(2, m) + m] % 3$。 - 。。。。。。 -- `f(n, m) = [f(n - 1, m) + m] % n `。 +- $f(n, m) = [f(n - 1, m) + m] % n $。 接下来就是递推求解了。 -## 代码 +### 思路 1:代码 ```python class Solution: @@ -50,6 +73,11 @@ class Solution: return ans ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + ## 参考资料: - [字节题库 - #剑62 - 简单 - 圆圈中最后剩下的数字 - 1刷](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/zi-jie-ti-ku-jian-62-jian-dan-yuan-quan-3hlji/) diff --git a/Templates/08.Graph/Graph-Prim.py b/Templates/08.Graph/Graph-Prim.py index 23f95f3e..d29693e6 100644 --- a/Templates/08.Graph/Graph-Prim.py +++ b/Templates/08.Graph/Graph-Prim.py @@ -1,32 +1,31 @@ class Solution: - def prim(self, graph): + # graph 为图的邻接矩阵,start 为起始顶点 + def prim(self, graph, start): size = len(graph) vis = set() dist = [float('inf') for _ in range(size)] - ans = 0 - pos = 0 - dist[pos] = 0 - vis.add(pos) + ans = 0 # 最小生成树的边权和 + dist[start] = 0 # 初始化起始顶点到起始顶点的边权值为 0 - for i in range(1, size): - if 0 in graph and i in graph[0]: - dist[i] = graph[0][i] + for i in range(1, size): # 初始化起始顶点到其他顶点的边权值 + dist[i] = graph[start][i] + vis.add(start) # 将 start 顶点标记为已访问 - for i in range(size - 1): - cur_min = float('inf') - pos = -1 - for j in range(size): - if j not in vis and dist[j] < cur_min: - cur_min = dist[j] - pos = j - if pos == -1: + for _ in range(size - 1): + min_dis = float('inf') + min_dis_pos = -1 + for i in range(size): + if i not in vis and dist[i] < min_dis: + min_dis = dist[i] + min_dis_pos = i + if min_dis_pos == -1: # 没有顶点可以加入 MST,图 G 不连通 return -1 - ans += cur_min - vis.add(pos) - for j in range(size): - if j not in vis and dist[j] > graph[pos][j]: - dist[j] = graph[pos][j] + ans += min_dis # 将顶点加入 MST,并将边权值加入到答案中 + vis.add(min_dis_pos) + for i in range(size): + if i not in vis and dist[i] > graph[min_dis_pos][i]: + dist[i] = graph[min_dis_pos][i] return ans points = [[0,0]]