一、问题描述
392.判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
如果有多个 s ,则需要判断它们是否都为 t 的子序列。
示例 1: 输入:s = "abc", t = "ahbgdc" 输出:true
示例 2: 输入:s = "axc", t = "ahbgdc" 输出:false
115.不同的子序列
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)。
题目数据保证答案符合 32 位带符号整数范围。
示例 1: 输入:s = "rabbbit", t = "rabbit" 输出:3
示例 2: 输入:s = "babgbag", t = "bag" 输出:5
二、解题思路
这两个问题都属于字符串问题,需要用到动态规划来解决。
- 392.判断子序列
定义状态:设 f[i][j] 表示字符串 t 中前 i 个字符和字符串 s 中前 j 个字符的最长公共子序列长度。
状态转移方程: 当 s[j] = t[i] 时,f[i][j] = f[i-1][j-1] + 1; 当 s[j] != t[i] 时,f[i][j] = f[i-1][j]。
最终答案为 f[m][n] 是否等于 s 的长度 n。
- 115.不同的子序列
定义状态:设 f[i][j] 表示字符串 t 中前 i 个字符中出现字符串 s 中前 j 个字符的不同子序列个数。
状态转移方程: 当 s[j] = t[i] 时,f[i][j] = f[i-1][j-1] + f[i-1][j]; 当 s[j] != t[i] 时,f[i][j] = f[i-1][j]。
最终答案为 f[m][n]。
三、代码实现
Java 代码如下:
- 392.判断子序列
public boolean isSubsequence(String s, String t) {
int m = s.length(), n = t.length();
if (m > n) {
return false;
}
int[][] f = new int[n + 1][m + 1];
for (int i = 0; i <= n; i++) {
f[i][0] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (t.charAt(i - 1) == s.charAt(j - 1)) {
f[i][j] = f[i - 1][j - 1] + 1;
} else {
f[i][j] = f[i - 1][j];
}
}
}
return f[n][m] == m;
}
- 115.不同的子序列
public int numDistinct(String s, String t) {
int m = s.length(), n = t.length();
int[][] f = new int[m + 1][n + 1];
for (int i = 0; i <= m; i++) {
f[i][0] = 1;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
} else {
f[i][j] = f[i - 1][j];
}
}
}
return f[m][n];
}
四、解题技巧
-
动态规划可以解决字符串的子序列问题,需要定义状态和状态转移方程,根据问题的特点进行设计。
-
在状态转移方程中,需要注意字符下标的对应关系,避免出现越界的情况。
-
在初始化状态时,需要特别注意边界情况,例如一个字符串为空字符串的情况。
-
对于字符串问题,需要注意空间复杂度的优化,可以使用滚动数组或者状态压缩等技巧来减少空间使用。
-
对于一些字符串操作,例如字符串的匹配、查找、替换等问题,可以使用 Java 中的 String 类提供的一些方法来实现,例如 indexOf、replace、matches 等。
-
在实际解决问题时,可以先对问题进行简化,例如只考虑字符串中是否存在某个子串,是否可以通过删除一些字符得到某个子序列等,然后再考虑更加复杂的情况。
-
在实现时可以使用一些工具类和数据结构,例如 StringBuilder、HashSet 等,来方便字符串操作和实现算法。
五、技术总结
本文介绍了使用动态规划来解决字符串的子序列问题,包括判断子序列和不同的子序列两个问题。对于这两个问题,本文分别给出了状态定义和状态转移方程,并使用 Java 代码进行实现。
在实现过程中,需要注意字符下标的对应关系,避免出现越界的情况,并且需要特别注意边界情况的处理。此外,在解决字符串问题时,需要注意空间复杂度的优化,可以使用滚动数组或者状态压缩等技巧来减少空间使用。
在实际解决问题时,可以先对问题进行简化,例如只考虑字符串中是否存在某个子串,是否可以通过删除一些字符得到某个子序列等,然后再考虑更加复杂的情况。此外,在实现时可以使用一些工具类和数据结构,例如 StringBuilder、HashSet 等,来方便字符串操作和实现算法。
总之,使用动态规划解决字符串的子序列问题是一种比较常见的解决方法,需要仔细思考问题的特点和算法的实现细节。