Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# LeetCode 题解(已完成 716 道)
# LeetCode 题解(已完成 717 道)

| 题号 | 标题 | 题解 | 标签 | 难度 |
| :------ | :------ | :------ | :------ | :------ |
Expand Down Expand Up @@ -257,6 +257,7 @@
| 0395 | [至少有 K 个重复字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0395.%20%E8%87%B3%E5%B0%91%E6%9C%89%20K%20%E4%B8%AA%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2.md) | 哈希表、字符串、分治、滑动窗口 | 中等 |
| 0399 | [除法求值](https://leetcode.cn/problems/evaluate-division/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0399.%20%E9%99%A4%E6%B3%95%E6%B1%82%E5%80%BC.md) | 深度优先搜索、广度优先搜索、并查集、图、数组、最短路 | 中等 |
| 0400 | [第 N 位数字](https://leetcode.cn/problems/nth-digit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0400.%20%E7%AC%AC%20N%20%E4%BD%8D%E6%95%B0%E5%AD%97.md) | 数学、二分查找 | 中等 |
| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 |
| 0404 | [左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0404.%20%E5%B7%A6%E5%8F%B6%E5%AD%90%E4%B9%8B%E5%92%8C.md) | 树 | 简单 |
| 0405 | [数字转换为十六进制数](https://leetcode.cn/problems/convert-a-number-to-hexadecimal) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0405.%20%E6%95%B0%E5%AD%97%E8%BD%AC%E6%8D%A2%E4%B8%BA%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E6%95%B0.md) | 位运算、数学 | 简单 |
| 0406 | [根据身高重建队列](https://leetcode.cn/problems/queue-reconstruction-by-height/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0406.%20%E6%A0%B9%E6%8D%AE%E8%BA%AB%E9%AB%98%E9%87%8D%E5%BB%BA%E9%98%9F%E5%88%97.md) | 贪心、数组、排序 | 中等 |
Expand Down
4 changes: 2 additions & 2 deletions Contents/00.Introduction/05.Categories-List.md
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@
| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 深度优先搜索、动态规划 | 中等 |
| 0576 | 出界的路径数 | | | |
| 0087 | 扰乱字符串 | | | |
| 0403 | 青蛙过河 | | | |
| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 |
| 0552 | 学生出勤记录 II | | | |
| 0913 | 猫和老鼠 | | | |
| 0329 | [矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0329.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、动态规划 | 困难 |
Expand All @@ -853,7 +853,7 @@
| 0338 | [比特位计数](https://leetcode.cn/problems/counting-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0338.%20%E6%AF%94%E7%89%B9%E4%BD%8D%E8%AE%A1%E6%95%B0.md) | 位运算、动态规划 | 简单 |
| 0045 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md) | 贪心、数组、动态规划 | 中等 |
| 0115 | [不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0115.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 困难 |
| 0403 | 青蛙过河 | | | |
| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 |
| 0576 | 出界的路径数 | | | |
| 0091 | [解码方法](https://leetcode.cn/problems/decode-ways/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0091.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95.md) | 字符串、动态规划 | 中等 |
| 0639 | 解码方法 II | | | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
| 0494 | [目标和](https://leetcode.cn/problems/target-sum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0494.%20%E7%9B%AE%E6%A0%87%E5%92%8C.md) | 深度优先搜索、动态规划 | 中等 |
| 0576 | 出界的路径数 | | | |
| 0087 | 扰乱字符串 | | | |
| 0403 | 青蛙过河 | | | |
| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 |
| 0552 | 学生出勤记录 II | | | |
| 0913 | 猫和老鼠 | | | |
| 0329 | [矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0329.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84.md) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、动态规划 | 困难 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
| 0338 | [比特位计数](https://leetcode.cn/problems/counting-bits/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0338.%20%E6%AF%94%E7%89%B9%E4%BD%8D%E8%AE%A1%E6%95%B0.md) | 位运算、动态规划 | 简单 |
| 0045 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0045.%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II.md) | 贪心、数组、动态规划 | 中等 |
| 0115 | [不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0115.%20%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 困难 |
| 0403 | 青蛙过河 | | | |
| 0403 | [青蛙过河](https://leetcode.cn/problems/frog-jump/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0403.%20%E9%9D%92%E8%9B%99%E8%BF%87%E6%B2%B3.md) | 数组、动态规划 | 困难 |
| 0576 | 出界的路径数 | | | |
| 0091 | [解码方法](https://leetcode.cn/problems/decode-ways/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0091.%20%E8%A7%A3%E7%A0%81%E6%96%B9%E6%B3%95.md) | 字符串、动态规划 | 中等 |
| 0639 | 解码方法 II | | | |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,4 @@
- [动态规划优化题目](./Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md)

## 11. 附加内容
## [12. LeetCode 题解(已完成 716 道)](./Contents/00.Introduction/04.Solutions-List.md)
## [12. LeetCode 题解(已完成 717 道)](./Contents/00.Introduction/04.Solutions-List.md)
133 changes: 121 additions & 12 deletions Solutions/0045. 跳跃游戏 II.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,127 @@

## 题目大意

给定一个非负整数数组 nums,数组中每个元素代表在该位置可以跳跃的最大长度。
**描述**:给定一个非负整数数组 `nums`,数组中每个元素代表在该位置可以跳跃的最大长度。开始位置为数组的第一个下标处

开始位置为数组的第一个下标处,目的是用最少的跳跃次数到达最后一个下标处
**要求**:计算出到达最后一个下标处的最少的跳跃次数。假设你总能到达数组的最后一个下标处

假设你总能到达数组的最后一个下标处。
**说明**:

- $1 \le nums.length \le 10^4$。
- $0 \le nums[i] \le 1000$。

**示例**:

```Python
输入 nums = [2,3,1,1,4]
输出 2
解释 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
```

## 解题思路

第 i 个位置所能跳到的位置为 `[i + 1, i + nums[i]]`,第 0 个位置所能跳到的位置就是 `[0 + 1, 0 + nums[0]]`,即 `[1, nums[0]]`,第 1 个位置所能跳到的位置就是 `[1 + 1, 1 + nums[1]]`,即 `[2, 1 + nums[1]]` ,等等。
### 思路 1:动态规划(超时)

###### 1. 划分阶段

按照位置进行阶段划分。

###### 2. 定义状态

定义状态 `dp[i]` 表示为:跳到下标 `i` 所需要的最小跳跃次数。

###### 3. 状态转移方程

对于当前位置 `i`,如果之前的位置 `j`($o \le j < i$) 能够跳到位置 `i` 需要满足:位置 `j`($o \le j < i$)加上位置 `j` 所能跳到的最远长度要大于等于 `i`,即 `j + nums[j] >= i` 。

而跳到下标 `i` 所需要的最小跳跃次数则等于满足上述要求的位置 `j` 中最小跳跃次数加 `1`,即 `dp[i] = min(dp[i], dp[j] + 1)`。

###### 4. 初始条件

对于每一个位置 i 来说,所能跳到的所有位置都可以作为下一个起跳点,为了尽可能使用最少的跳跃次数,所以我们应该使得下一次起跳所能达到的位置尽可能的远。简单来说,就是每次在「可跳范围」内选择可以使下一次跳的更远的位置
初始状态下,跳到下标 `0` 需要的最小跳跃次数为 `0`,即 `dp[0] = 0`

在实现中,我们维护当前所能达到的最远位置 end,下一步所能跳到的最远位置 max_pos,最少跳跃次数 setps。
##### 5. 最终结果

- 遍历 nums 的前 len(nums) - 1 个元素,更新 end 位置上下一步所能跳到的最远位置 max_pos。
- max_pos 为 `[i + 1, i + nums[i]]` 范围内,所能跳到最远位置。
- 如果索引 i 到达了 end 边界,则:
- 更新 end 为新的当前位置,并令步数 setps + 1。
- 最终返回跳跃次数 steps。
根据我们之前定义的状态,`dp[i]` 表示为:跳到下标 `i` 所需要的最小跳跃次数。则最终结果为 `dp[size - 1]`。

## 代码
### 思路 1:动态规划(超时)代码

```Python
class Solution:
def jump(self, nums: List[int]) -> int:
size = len(nums)
dp = [float("inf") for _ in range(size)]
dp[0] = 0

for i in range(1, size):
for j in range(i):
if j + nums[j] >= i:
dp[i] = min(dp[i], dp[j] + 1)

return dp[size - 1]
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总体时间复杂度为 $O(n^2)$。
- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。

### 思路 2:动态规划 + 贪心

因为本题的数据规模为 $10^4$,而思路 1 的时间复杂度是 $O(n^2)$,所以就超时了。那么我们有什么方法可以优化一下,减少一下时间复杂度吗?

上文提到,在满足 `j + nums[j] >= i` 的情况下,`dp[i] = min(dp[i], dp[j] + 1)`。

通过观察可以发现,`dp[i]` 是单调递增的,也就是说 `dp[i - 1] <= dp[i] <= dp[i + 1]`。

举个例子,比如跳到下标 `i` 最少需要 `5` 步,即 `dp[i] = 5`,那么必然不可能出现少于 `5` 步就能跳到下标 `i + 1` 的情况,跳到下标 `i + 1` 至少需要 `5` 步或者更多步。

既然 `dp[i]` 是单调递增的,那么在更新 `dp[i]` 时,我们找到最早可以跳到 `i` 的点 `j`,从该点更新 `dp[i]`。即找到满足 `j + nums[j] >= i` 的第一个 `j`,使得 `dp[i] = dp[j] + 1`。

而查找第一个 `j` 的过程可以通过使用一个指针变量 `j` 从前向后迭代查找。

最后,将最终结果 `dp[size - 1]` 返回即可。

### 思路 2:动态规划 + 贪心代码

```Python
class Solution:
def jump(self, nums: List[int]) -> int:
size = len(nums)
dp = [float("inf") for _ in range(size)]
dp[0] = 0

j = 0
for i in range(1, size):
while j + nums[j] < i:
j += 1
dp[i] = dp[j] + 1

return dp[size - 1]
```

### 思路 2:复杂度分析

- **时间复杂度**:$O(n)$。最外层循环遍历的时间复杂度是 $O(n)$,看似和内层循环结合遍历的时间复杂度是 $O(n^2)$,实际上内层循环只遍历了一遍,与外层循环遍历次数是相加关系,两者的时间复杂度和是 $O(2n)$,$O(2n) = O(n)$,所以总体时间复杂度为 $O(n)$。
- **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。

### 思路 2:贪心算法

如果第 `i` 个位置所能跳到的位置为 `[i + 1, i + nums[i]]`,则:

- 第 `0` 个位置所能跳到的位置就是 `[0 + 1, 0 + nums[0]]`,即 `[1, nums[0]]`。
- 第 `1` 个位置所能跳到的位置就是 `[1 + 1, 1 + nums[1]]`,即 `[2, 1 + nums[1]]`。
- ……

对于每一个位置 `i` 来说,所能跳到的所有位置都可以作为下一个起跳点,为了尽可能使用最少的跳跃次数,所以我们应该使得下一次起跳所能达到的位置尽可能的远。简单来说,就是每次在「可跳范围」内选择可以使下一次跳的更远的位置。这样才能获得最少跳跃次数。具体做法如下:

1. 维护几个变量:当前所能达到的最远位置 `end`,下一步所能跳到的最远位置 `max_pos`,最少跳跃次数 `setps`。
2. 遍历数组 `nums` 的前 `len(nums) - 1` 个元素:
1. 每次更新第 `i` 位置下一步所能跳到的最远位置 `max_pos`。
2. 如果索引 `i` 到达了 `end` 边界,则:更新 `end` 为新的当前位置 `max_pos`,并令步数 `setps` 加 `1`。
3. 最终返回跳跃次数 `steps`。

### 思路 2:贪心算法代码

```Python
class Solution:
Expand All @@ -40,3 +140,12 @@ class Solution:
return steps
```

### 思路 2:复杂度分析

- **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度是 $O(n)$,所以总体时间复杂度为 $O(n)$。
- **空间复杂度**:$O(1)$。只用到了常数项的变量,所以总体空间复杂度为 $O(1)$。

## 参考资料

- 【题解】[【宫水三叶の相信科学系列】详解「DP + 贪心 + 双指针」解法,以及该如何猜 DP 的状态定义 - 跳跃游戏 II - 力扣](https://leetcode.cn/problems/jump-game-ii/solution/xiang-jie-dp-tan-xin-shuang-zhi-zhen-jie-roh4/)
- 【题解】[动态规划+贪心,易懂。 - 跳跃游戏 II - 力扣](https://leetcode.cn/problems/jump-game-ii/solution/dong-tai-gui-hua-tan-xin-yi-dong-by-optimjie/)
48 changes: 41 additions & 7 deletions Solutions/0115. 不同的子序列.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,60 @@

## 题目大意

给定两个字符串 `s` 和 `t`。要求:计算在 `s` 的子序列中 `t` 出现的个数。
**描述**:给定两个字符串 `s` 和 `t`。

**要求**:计算在 `s` 的子序列中 `t` 出现的个数。

**说明**:

- **字符串的子序列**:通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,`"ACE"` 是 `"ABCDE"` 的一个子序列,而 `"AEC"` 不是)。
- $0 \le s.length, t.length \le 1000$。
- `s` 和 `t` 由英文字母组成。

**示例**:

```Python
输入 s = "rabbbit", t = "rabbit"
输出 3
解释 如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
```

$\underline{rabb}b\underline{it}$
$\underline{ra}b\underline{bbit}$
$\underline{rab}b\underline{bit}$

## 解题思路

动态规划求解。
### 思路 1:动态规划

###### 1. 划分阶段

按照子序列的结尾位置进行阶段划分。

###### 2. 定义状态

定义状态 `dp[i][j]`表示为: `i - 1` 为结尾的 `s` 子序列中出现以 `j - 1` 为结尾的 `t` 的个数。
定义状态 `dp[i][j]` 表示为:以第 `i - 1` 个字符为结尾的 `s` 子序列中出现以第 `j - 1` 个字符为结尾的 `t` 的个数。

则状态转移方程为:
###### 3. 状态转移方程

双重循环遍历字符串 `s` 和 `t`,则状态转移方程为:

- 如果 `s[i - 1] == t[j - 1]`,则:`dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]`。即 `dp[i][j]` 来源于两部分:
- 使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 2` 为结尾的 `t` 的个数,即 `dp[i - 1][j - 1]`。
- 不使用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于以 `i - 2` 为结尾的 `s` 子序列中出现以 `j - 1` 为结尾的 `t` 的个数,即 `dp[i - 1][j]`。
- 如果 `s[i - 1] != t[j - 1]`,那么肯定不能用 `s[i - 1]` 匹配 `t[j - 1]`,则 `dp[i][j]` 取源于 `dp[i - 1][j]`。

下面来看看初始化:
###### 4. 初始条件

- `dp[i][0]` 表示以 `i - 1` 为结尾的 `s` 子序列中出现空字符串的个数。把 `s` 中的元素全删除,出现空字符串的个数就是 `1`,则 `dp[i][0] = 1`。
- `dp[0][j]` 表示空字符串中出现以 `j - 1` 结尾的 `t` 的个数,空字符串无论怎么变都不会变成 `t`,则 `dp[0][j] = 0`
- `dp[0][0]` 表示空字符串中出现空字符串的个数,这个应该是 `1`,即 `dp[0][0] = 1`。

然后递推求解,最后输出 `dp[size_s][size_t]`。
##### 5. 最终结果

根据我们之前定义的状态,`dp[i][j]` 表示为:以第 `i - 1` 个字符为结尾的 `s` 子序列中出现以第 `j - 1` 个字符为结尾的 `t` 的个数。则最终结果为 `dp[size_s][size_t]`,将其返回即可。

## 代码
### 思路 1:动态规划代码

```Python
class Solution:
Expand All @@ -47,3 +77,7 @@ class Solution:
return dp[size_s][size_t]
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。
- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。
Loading