115. 不同的子序列¶
难度:困难
题目¶
给定一个字符串 s
和一个字符串 t
,计算在 s
的子序列中 t
出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE"
是 "ABCDE"
的一个子序列,而 "AEC"
不是)
题目数据保证答案符合 32 位带符号整数范围。
示例 1:
输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
(上箭头符号 ^ 表示选取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
示例 2:
输入:s = "babgbag", t = "bag"
输出:5
解释:
如下图所示, 有 5 种可以从 s 中得到 "bag" 的方案。
(上箭头符号 ^ 表示选取的字母)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
提示:
0 <= s.length, t.length <= 1000
s
和t
由英文字母组成
题解¶
解题思路:设\(f(x, y)\)为字符串\(x\)中的子序列中字符串\(y\)出现的次数。并且使用\(x + 1\)表示字符串\(x\)从第二个字符开始的子串。
则:
- 当\(x, y\)的第一个字符不同时,有\(f(x, y) = f(x + 1, y)\)
- 当\(x, y\)的第一个字符匹配时,有\(f(x, y) = f(x + 1, y + 1) + f(x + 1, y)\)
特殊地,当\(y\)为空字符串时,说明字符串匹配结束,对应一个匹配成功的子串,因此\(f(x, \varnothing) = 1\)。当\(x\)为空字符串且\(y\)不为空字符串时,说明字符串匹配失败,\(f(\varnothing, y) = \left\{\begin{aligned}&0 &y\not = \varnothing \\ & 1 &y=\varnothing\end{aligned}\right .\)。
由此,可以初步得出递归代码:
int numDistinct(char * s, char * t){
if (!*t)
return 1;
if (!*s)
return 0;
int ret = 0;
if (*s == *t)
ret += numDistinct(s + 1, t + 1);
ret += numDistinct(s + 1, t);
return ret;
}
如上代码会造成超时,原因是递归时出现了分支导致时间复杂度为\(2^n\)。使用数组存储历史结果可以避免递归产生分支,优化时间复杂度:
int cached(char *s, char *t, int x, int y, int **buffer) {
int ret = 0;
if (!*t)
ret = 1;
else if (!*s)
ret = 0;
else
{
if (buffer[x][y] >= 0)
return buffer[x][y];
if (*s == *t)
ret += cached(s + 1, t + 1, x + 1, y + 1, buffer);
ret += cached(s + 1, t, x + 1, y, buffer);
buffer[x][y] = ret;
}
return ret;
}
int numDistinct(char * s, char * t){
int x = strlen(s), y = strlen(t);
int *_buffer = (int *)malloc(sizeof(int) * x * y);
int **buffer = (int **)malloc(sizeof(int *) * x);
for (int i = 0; i < x; i++)
buffer[i] = _buffer + i * y;
memset(_buffer, 0xff, sizeof(int) * x * y);
return cached(s, t, 0, 0, buffer);
}