[LeetCode] 115. Distinct Subsequences 不同的子序列
Given two strings s
and t
, return the number of distinct subsequences of s
which equals t
.
The test cases are generated so that the answer fits on a 32-bit signed integer.
Example 1:
Input: s = "rabbbit", t = "rabbit" Output: 3 Explanation: As shown below, there are 3 ways you can generate "rabbit" from s. rabbbit rabbbit rabbbit
Example 2:
Input: s = "babgbag", t = "bag" Output: 5 Explanation: As shown below, there are 5 ways you can generate "bag" from s. babgbag babgbag babgbag babgbag babgbag
Constraints:
1 <= s.length, t.length <= 1000
s
andt
consist of English letters.
看到有关字符串的子序列或者配准类的问题,首先应该考虑的就是用动态规划 Dynamic Programming 来求解,这个应成为条件反射。而所有 DP 问题的核心就是找出状态转移方程,想这道题就是递推一个二维的 dp 数组,其中 dp[i][j] 表示s中前j个字符形成的的子串中能组成t中前i个字符形成的子串的子序列的个数。下面从题目中给的例子来分析,这个二维 dp 数组应为:
Ø r a b b b i t Ø 1 1 1 1 1 1 1 1 r 0 1 1 1 1 1 1 1 a 0 0 1 1 1 1 1 1 b 0 0 0 1 2 3 3 3 b 0 0 0 0 1 3 3 3 i 0 0 0 0 0 0 3 3 t 0 0 0 0 0 0 0 3
首先,若原字符串和子序列都为空时,返回1,因为空串也是空串的一个子序列。若原字符串不为空,而子序列为空,也返回1,因为空串也是任意字符串的一个子序列。而当原字符串为空,子序列不为空时,返回0,因为非空字符串不能当空字符串的子序列。理清这些,二维数组 dp 的边缘便可以初始化了,下面只要找出状态转移方程,就可以更新整个 dp 数组了。我们通过观察上面的二维数组可以发现,当更新到 dp[i][j] 时,dp[i][j] >= dp[i][j - 1] 总是成立,再进一步观察发现,当 T[i - 1] == S[j - 1] 时,dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1],若不等, dp[i][j] = dp[i][j - 1],所以,综合以上,递推式为:
dp[i][j] = dp[i][j - 1] + (T[i - 1] == S[j - 1] ? dp[i - 1][j - 1] : 0)
根据以上分析,可以写出代码如下:
class Solution { public: int numDistinct(string s, string t) { int m = s.size(), n = t.size(); vector<vector<unsigned long>> dp(n + 1, vector<unsigned long>(m + 1)); for (int j = 0; j <= m; ++j) dp[0][j] = 1; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { dp[i][j] = dp[i][j - 1] + (t[i - 1] == s[j - 1] ? dp[i - 1][j - 1] : 0); } } return dp[n][m]; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/115
参考资料:
https://leetcode.com/problems/distinct-subsequences/
https://leetcode.com/problems/distinct-subsequences/discuss/37327/Easy-to-understand-DP-in-Java