- **比较两个字符串**的题目,属于处理“两个对等数组”,类似的题目做过多次后,我们会形成使用`二维数组`进行动态规划的直觉。
## “动态规划”的模式
“动态规划”,需要用`dp`数组来保存结果。`dp[i][j]`的值可以由它的前一个(或多个)值通过公式转化出来。因此,`dp[i][j]`值是一步一步推导出来的,它和先前的`dp`记录值都有联系。
#### “动态规划”分为五步
1. 确定数组`dp`的每个值代表的**含义**。
2. 初始化数组`dp`的值。
3. 根据一个示例,**按顺序**填入`dp`网格数据。
4. 根据`dp`网格数据,推导出**递推公式**。
5. 写出程序,并打印`dp`数组,不合预期就调整。
#### 细说这五步
1. 确定数组`dp`的每个值代表的**含义**。
- 先确定`dp`是一维数组还是二维数组。“一维滚动数组”意味着每次迭代时都会覆盖数组的值。大多时候,用“一维滚动数组”代替“二维数组”可以简化代码;但有些题目,比如要操作“两个位置可互换的数组”,为了理解方便,还是使用“二维数组”。
- 尝试使用问题所求的`返回值`的含义作为 `dp[i]`(一维)或`dp[i][j]`(二维)的含义,约60%的概率能行。如果不行,再尝试其他含义。
- 设计上尽量考虑保存更丰富的信息,重复信息只在某个`dp[i]`中保存一次就够了。
- 使用简化的含义。如果用`布尔值`可以解决问题,就不要用`数值`。
2. 初始化数组`dp`的值。`dp`的值涉及两个层面:
1. `dp`的长度。通常是:`条件数组长度加1`或`条件数组长度`。
2. `dp[i]`或`dp[i][j]`的值。`dp[0]`或`dp[0][0]`有时需要特殊处理。
3. 根据一个示例,**按顺序**填入`dp`网格数据。
- “递推公式”是“动态规划”算法的核心。但“递推公式”是隐晦的,想得到它,就需要制表,用数据启发自己。
- 如果原示例不够好,需要自己重新设计一个。
- 根据示例,填入`dp`网格数据,需要“按顺序”填,这是很重要的,因为它决定了代码的遍历顺序。
- 大多时候,从左到右,从上到下。但有时需要从右向左、由下而上、从中间向右(或左),如“回文串”问题。有时,还需要一行遍历两次,先正向,再反向。
- 当顺序决定对了,起点就决定好了,从起点出发,“按顺序”填写`dp`网格数据,这也是在模拟程序处理的过程。
- 在此过程中,您将获得写出“递推公式”的灵感。如果您已经能推导出公式,不需要填完网格。
4. 根据`dp`网格数据,推导出**递推公式**。
- 有三个特别的位置需要注意: `dp[i - 1][j - 1]`、`dp[i - 1][j]`和`dp[i][j - 1]`,当前的 `dp[i][j]`往往取决于它们。
- 操作“两个位置可互换的数组”时,因为对称性,我们可能需要同时使用`dp[i - 1][j]`和`dp[i][j - 1]`。
5. 写出程序,并打印`dp`数组,不合预期就调整。
- 重点分析那些不合预期的数值。
读完了上面的内容,是不是感觉“动态规划”也没有那么难了?试着解出这道题吧。🤗
## 步骤
1. 确定 `dp[i][j]` 的**含义**。
- `dp[i][j]` 表示 `s` 的前 `i` 个字母是否是 `t` 的前 `j` 个字母的子序列。
- `dp[i][j]` 为 `true` 或 `false`。
2. 确定 `dp` 数组的初始值。
- 使用示例:
```
After initialization, the 'dp' array would be:
s = "abc", t = "ahbgdc"
# a h b g d c
# T T T T T T T # dp[0]
# a F F F F F F F
# b F F F F F F F
# c F F F F F F F
```
- `dp[0][j] = true` 因为 `dp[0]` 表示空字符串,而空字符串是任何字符串的子序列。
- `dp[i][j] = false (i != 0)`。
3. 根据一个示例,“按顺序”填入`dp`网格数据。
```
1. s = "a", t = "ahbgdc"
# a h b g d c
# T T T T T T T
# a F T T T T T T # dp[1]
```
```
2. s = "ab", t = "ahbgdc"
# a h b g d c
# T T T T T T T
# a F T T T T T T
# b F F F T T T T
```
```
3. s = "abc", t = "ahbgdc"
# a h b g d c
# T T T T T T T
# a F T T T T T T
# b F F F T T T T
# c F F F F F F T # dp[3]
```
4. 根据`dp`网格数据,推导出“递推公式”。
```ruby
if s[i - 1] == t[j - 1]
dp[i][j] = dp[i - 1][j - 1]
else
dp[i][j] = dp[i][j - 1]
end
```
5. 检查 `dp` 数组的值
- 打印 `dp` 以查看它是否符合预期。
## 复杂度
- 时间复杂度: `O(N * M)`.
- 空间复杂度: `O(N * M)`.
## Python
```python
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
column_count = len(t) + 1
dp = [[True] * column_count]
for _ in s:
dp.append([False] * column_count)
for i in range(1, len(dp)):
for j in range(1, len(dp[0])):
if s[i - 1] == t[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = dp[i][j - 1]
return dp[-1][-1]
```
## C++
```cpp
class Solution {
public:
bool isSubsequence(string s, string t) {
vector> dp(s.size() + 1, vector(t.size() + 1));
fill(dp[0].begin(), dp[0].end(), true);
for (auto i = 1; i < dp.size(); i++) {
for (auto j = 1; j < dp[0].size(); j++) {
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = dp[i][j - 1];
}
}
}
return dp[dp.size() - 1][dp[0].size() - 1];
}
};
```
## JavaScript
```javascript
var isSubsequence = function (s, t) {
const dp = Array(s.length + 1).fill().map(
() => Array(t.length + 1).fill(false)
)
dp[0].fill(true)
for (let i = 1; i < dp.length; i++) {
for (let j = 1; j < dp[0].length; j++) {
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1]
} else {
dp[i][j] = dp[i][j - 1]
}
}
}
return dp.at(-1).at(-1)
};
```
## Go
```go
func isSubsequence(s string, t string) bool {
dp := make([][]bool, len(s) + 1)
columnSize := len(t) + 1
dp[0] = slices.Repeat([]bool{true}, columnSize)
for i := 1; i < len(dp); i++ {
dp[i] = make([]bool, columnSize)
}
for i := 1; i < len(dp); i++ {
for j := 1; j < len(dp[0]); j++ {
if s[i - 1] == t[j - 1] {
dp[i][j] = dp[i - 1][j - 1]
} else {
dp[i][j] = dp[i][j - 1]
}
}
}
return dp[len(dp) - 1][len(dp[0]) - 1]
}
```
## Java
```java
class Solution {
public boolean isSubsequence(String s, String t) {
var dp = new boolean[s.length() + 1][t.length() + 1];
Arrays.fill(dp[0], true);
for (var i = 1; i < dp.length; i++) {
for (var j = 1; j < dp[0].length; j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = dp[i][j - 1];
}
}
}
return dp[dp.length - 1][dp[0].length - 1];
}
}
```
## C#
```csharp
public class Solution
{
public bool IsSubsequence(string s, string t)
{
var dp = new bool[s.Length + 1, t.Length + 1];
for (var j = 0; j < dp.GetLength(1); j++)
dp[0, j] = true;
for (var i = 1; i < dp.GetLength(0); i++)
{
for (var j = 1; j < dp.GetLength(1); j++)
{
if (s[i - 1] == t[j - 1])
{
dp[i, j] = dp[i - 1, j - 1];
}
else
{
dp[i, j] = dp[i, j - 1];
}
}
}
return dp[dp.GetUpperBound(0), dp.GetUpperBound(1)];
}
}
```
## Ruby
```ruby
def is_subsequence(s, t)
dp = Array.new(s.size + 1) do |i|
Array.new(t.size + 1, i == 0 ? true : false)
end
(1...dp.size).each do |i|
(1...dp[0].size).each do |j|
dp[i][j] =
if s[i - 1] == t[j - 1]
dp[i - 1][j - 1]
else
dp[i][j - 1]
end
end
end
dp[-1][-1]
end
```
## Other languages
```java
// Welcome to create a PR to complete the code of this language, thanks!
```
亲爱的力扣人,为了您更好的刷题体验,请访问 [LeetCodePython.com](https://leetcodepython.com/zh)。
本站敢称力扣题解最佳实践,终将省你大量刷题时间!
原文链接:[392. 判断子序列 - LeetCode Python/Java/C++ 题解](https://leetcodepython.com/zh/leetcode/392-is-subsequence).
GitHub 仓库: [f*ck-leetcode](https://github.com/fuck-leetcode/fuck-leetcode).