首先,尝试自己解决问题并得出一个简单的实现:
假设 S.length = m,T.length = n。我们用 S{i} 来表示从 i 开始的子字符串 S。例如,如果 S = "abcde",则 S{0} = "abcde",S{4} = "e",S{5} = ""。我们对 T 使用类似的定义。
令 N[i][j] 表示 S{i} 和 T{j} 的不同子序列。我们感兴趣的是 N[0][0](因为它们都是完整的字符串)。
有两种简单情况:对于任何 i,N[i][n] 和对于任何 j
现在,给定任意的 i 和 j,我们需要找到一个递归公式。有两种情况。
如果 S[i] != T[j],我们知道 N[i][j] = N[i+1][j](我希望您可以自行验证此内容,我旨在详细解释上面的加密算法,而不是这个简单版本)。
如果 S[i] = T[j],我们有一个选择。我们可以“匹配”这些字符并继续处理 S 和 T 的下一个字符,或者我们可以忽略这个匹配(就像 S[i] != T[j] 的情况一样)。由于我们有这两种选择,我们需要将它们的计数相加:N[i][j] = N[i+1][j] + N[i+1][j+1]。
为了使用动态规划找到 N[0][0],我们需要填充 N 表。我们首先需要设置表的边界:
N[m][j] = 0, for 0 <= j < n
N[i][n] = 1, for 0 <= i <= m
由于递归关系中存在依赖性,我们可以通过向后循环
i 并向前循环
j 来填充表的其余部分。
for (int i = m-1; i >= 0; i--) {
for (int j = 0; j < n; j++) {
if (S[i] == T[j]) {
N[i][j] = N[i+1][j] + N[i+1][j+1];
} else {
N[i][j] = N[i+1][j];
}
}
}
现在我们可以使用算法中最重要的技巧:我们可以使用一个一维数组f
,并在外层循环中使用不变量:f = N[i+1];
这是由于表格填充的方式所致。如果应用到我的算法中,这将如下:
f[j] = 0, for 0 <= j < n
f[n] = 1
for (int i = m-1; i >= 0; i--) {
for (int j = 0; j < n; j++) {
if (S[i] == T[j]) {
f[j] = f[j] + f[j+1];
} else {
f[j] = f[j];
}
}
}
我们已经接近您提供的算法了。首先,我们不需要初始化f[j] = 0
。其次,我们不需要像f[j] = f[j]
这样的赋值。
由于这是C++
代码,我们可以重写这段代码。
if (S[i] == T[j]) {
f[j] += f[j+1];
}
to
f[j] += (S[i] == T[j]) * f[j+1];
就是这样。这样就得到了算法:
f[n] = 1
for (int i = m-1; i >= 0; i--) {
for (int j = 0; j < n; j++) {
f[j] += (S[i] == T[j]) * f[j+1];
}
}
for(int i = 0; i <= m; i++) { N[i][n] = 1; }
。最大的区别在于这种方式是操作性的:我提供了一个“算法”来设置值,而帖子中的方式是声明性的:我只关心值,不关心如何实现它们。这是一种更数学化的写法。 - Vincent van der WeeleS
的某些字母与T
的字母匹配,而有些则不匹配。该算法循环遍历S
的字母,并在它匹配或不匹配T
中的字母时累加结果。没有第三种选择,因此不会重复计数。希望这不会让你更加困惑;-) - Vincent van der Weele