diff --git a/Contents/00.Introduction/04.Solutions-List.md b/Contents/00.Introduction/04.Solutions-List.md index 5f807fba..7da3fd22 100644 --- a/Contents/00.Introduction/04.Solutions-List.md +++ b/Contents/00.Introduction/04.Solutions-List.md @@ -1,4 +1,4 @@ -# LeetCode 题解(已完成 716 道) +# LeetCode 题解(已完成 717 道) | 题号 | 标题 | 题解 | 标签 | 难度 | | :------ | :------ | :------ | :------ | :------ | @@ -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) | 贪心、数组、排序 | 中等 | diff --git a/Contents/00.Introduction/05.Categories-List.md b/Contents/00.Introduction/05.Categories-List.md index 0d63774f..434a3ae6 100644 --- a/Contents/00.Introduction/05.Categories-List.md +++ b/Contents/00.Introduction/05.Categories-List.md @@ -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) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、动态规划 | 困难 | @@ -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 | | | | diff --git a/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md b/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md index b867ffef..dd480fc2 100644 --- a/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md +++ b/Contents/10.Dynamic-Programming/02.Memoization/02.Memoization-List.md @@ -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) | 深度优先搜索、广度优先搜索、图、拓扑排序、记忆化搜索、动态规划 | 困难 | diff --git a/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-List.md b/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-List.md index c687646d..390dc66c 100644 --- a/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-List.md +++ b/Contents/10.Dynamic-Programming/03.Linear-DP/02.Linear-DP-List.md @@ -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 | | | | diff --git a/README.md b/README.md index f8771b16..623d6ac4 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 题解(已完成 716 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file +## [12. LeetCode 题解(已完成 717 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file 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 93a5f474..215b536b 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" @@ -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: @@ -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/) diff --git "a/Solutions/0115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.md" "b/Solutions/0115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.md" index 1edbce61..2cbe5d1e 100644 --- "a/Solutions/0115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.md" +++ "b/Solutions/0115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227.md" @@ -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: @@ -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)$。 diff --git "a/Solutions/0338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260.md" "b/Solutions/0338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260.md" index f9dd5fea..fd0087d4 100644 --- "a/Solutions/0338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260.md" +++ "b/Solutions/0338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260.md" @@ -5,21 +5,65 @@ ## 题目大意 -给定一个整数 `n`。 +**描述**:给定一个整数 `n`。 -要求:对于 `0 ≤ i ≤ n` 的每一个 `i`,计算其二进制表示中 `1` 的个数,返回一个长度为 `n + 1` 的数组 `ans` 作为答案。 +**要求**:对于 `0 ≤ i ≤ n` 的每一个 `i`,计算其二进制表示中 `1` 的个数,返回一个长度为 `n + 1` 的数组 `ans` 作为答案。 + +**说明**: + +- $0 \le n \le 10^5$。 +- 使用线性时间复杂度 $O(n)$ 解决此问题。 +- 不使用任何内置函数解决此问题。 + +**示例**: + +```Python +输入 n = 5 +输出 [0,1,1,2,1,2] +解释 +0 --> 0 +1 --> 1 +2 --> 10 +3 --> 11 +4 --> 100 +5 --> 101 +``` ## 解题思路 -可以根据整数的二进制特点将其分为两类: +### 思路 1:动态规划 + +根据整数的二进制特点可以将整数分为两类: + +- 奇数:其二进制表示中 `1` 的个数一定比前面相邻的偶数多一个 `1`。 +- 偶数:其二进制表示中 `1` 的个数一定与该数除以 `2` 之后的数一样多。 -- 奇数:一定比前面相邻的偶数多一个 `1`。 -- 偶数:一定和除以 `2` 之后的数一样多。 -- 边界 `0`:`1` 的个数为 `0`。 +另外,边界 `0` 的二进制表示中 `1` 的个数为 `0`。 于是可以根据规律,从 `0` 开始到 `n` 进行递推求解。 -## 代码 +###### 1. 划分阶段 + +按照整数 `n` 进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `dp[i]` 表示为:整数 `i` 对应二进制表示中 `1` 的个数。 + +###### 3. 状态转移方程 + +- 如果 `i` 为奇数,则整数 `i` 对应二进制表示中 `1` 的个数等于整数 `i - 1` 对应二进制表示中 `1` 的个数加 `1`,即 `dp[i] = dp[i - 1] + 1`。 +- 如果 `i` 为偶数,则整数 `i` 对应二进制表示中 `1` 的个数等于整数 `i // 2` 对应二进制表示中 `1` 的个数,即 `dp[i] = dp[i // 2]`。 + +###### 4. 初始条件 + +整数 `0` 对应二进制表示中 `1` 的个数为 `0`。 + +##### 5. 最终结果 + +整个 `dp` 数组即为最终结果,将其返回即可。 + +### 思路 1:动态规划代码 ```Python class Solution: @@ -33,3 +77,8 @@ class Solution: return dp ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。一重循环的时间复杂度为 $O(n)$。 +- **空间复杂度**:$O(n)$。用到了一位数组保存状态,所以总的时间复杂度为 $O(n)$。 + 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" new file mode 100644 index 00000000..5f4f84cc --- /dev/null +++ "b/Solutions/0403. \351\235\222\350\233\231\350\277\207\346\262\263.md" @@ -0,0 +1,103 @@ +# [0403. 青蛙过河](https://leetcode.cn/problems/frog-jump/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目大意 + +**描述**:一只青蛙要过河,这条河被等分为若干个单元格,每一个单元格内可能放油一块石子(也可能没有)。青蛙只能跳到有石子的单元格内,不能跳到没有石子的单元格内。 + +现在给定一个严格按照升序排序的数组 `stones`,其中 `stones[i]` 代表第 `i` 块石子所在的单元格序号。默认第 `0` 块石子序号为 `0`(即 `stones[0] == 0`)。 + +开始时,青蛙默认站在序号为 `0` 石子上(即 `stones[0]`),并且假定它第 `1` 步只能跳跃 `1` 个单位(即只能从序号为 `0` 的单元格跳到序号为 `1` 的单元格)。 + +如果青蛙在上一步向前跳跃了 `k` 个单位,则下一步只能向前跳跃 `k - 1`、`k` 或者 `k + 1` 个单位。 + +**要求**:判断青蛙能否成功过河(即能否在最后一步跳到最后一块石子上)。如果能,则返回 `True`;否则,则返回 `False`。 + +**说明**: + +- $2 \le stones.length \le 2000$。 +- $0 \le stones[i] \le 2^{31} - 1$。 +- $stones[0] == 0$。 +- $stones$ 按严格升序排列。 + +**示例**: + +```Python +输入 stones = [0,1,3,5,6,8,12,17] +输出 true +解释 青蛙可以成功过河,按照如下方案跳跃:跳 1 个单位到第 2 块石子, 然后跳 2 个单位到第 3 块石子, 接着 跳 2 个单位到第 4 块石子, 然后跳 3 个单位到第 6 块石子, 跳 4 个单位到第 7 块石子, 最后,跳 5 个单位到第 8 个石子(即最后一块石子)。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +题目中说:如果青蛙在上一步向前跳跃了 `k` 个单位,则下一步只能向前跳跃 `k - 1`、`k` 或者 `k + 1` 个单位。则下一步的状态可以由 `3` 种状态转移而来。 + +- 上一步所在石子到下一步所在石头的距离为 `k - 1`。 +- 上一步所在石子到下一步所在石头的距离为 `k`。 +- 上一步所在石子到下一步所在石头的距离为 `k + 1`。 + +则我们可以通过石子块数,跳跃距离来进行阶段划分和定义状态,以及推导状态转移方程。 + +###### 1. 划分阶段 + +按照石子块数进行阶段划分。 + +###### 2. 定义状态 + +定义状态 `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] `。 + +###### 4. 初始条件 + +刚开始青蛙站在序号为 `0` 石子上(即 `stones[0]`),肯定能以长度为 `0` 的距离,到达第 `0` 块石子,即 `dp[0][0] = True`。 + +##### 5. 最终结果 + +根据我们之前定义的状态,`dp[i][k]` 表示为:青蛙能否以长度为 `k` 的距离,到达第 `i` 块石子。则如果 `dp[size - 1][k]` 为真,则说明青蛙能成功过河(即能在最后一步跳到最后一块石子上);否则则说明青蛙不能成功过河。 + +### 思路 1:动态规划代码 + +```Python +class Solution: + def canCross(self, stones: List[int]) -> bool: + size = len(stones) + + stone_dict = dict() + for i in range(size): + stone_dict[stones[i]] = i + + dp = [[False for _ in range(size + 1)] for _ in range(size)] + dp[0][0] = True + + for i in range(1, size): + for j in range(i): + k = stones[i] - stones[j] + if k <= 0 or k > j + 1: + continue + + dp[i][k] = dp[j][k - 1] or dp[j][k] or dp[j][k + 1] + + if dp[size - 1][k]: + return True + + return False +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。两重循环遍历的时间复杂度是 $O(n^2)$,所以总的时间复杂度为 $O(n^2)$。 +- **空间复杂度**:$O(n^2)$。用到了二维数组保存状态,所以总体空间复杂度为 $O(n^2)$。 + +## 参考资料 + +- 【题解】[【403. 青蛙过河】理解理解动态规划与dfs - 青蛙过河 - 力扣](https://leetcode.cn/problems/frog-jump/solution/403-qing-wa-guo-he-li-jie-li-jie-dong-ta-oyt9/) \ No newline at end of file