动态规划背后的想法是反向评估递归函数,从基本情况开始,迭代地为越来越大的子问题建立答案,直到为整个输入问题计算出答案。
如果您正在递归地评估此函数,那么在您提到的情况下,您绝对会通过递减 i 和 j 来递归。但是,在动态编程版本中,您不是从 LCS[i, j] 开始并尝试通过评估 LCS[i-1, j-1] 来评估它。您从 LCS[i-1, j-1] 开始并使用它来评估 LCS[i, j]。
具体来说,这段代码所做的是首先通过直接使用基本案例解决方案为所有 i 和 j 计算 LCS[i, 0] 和 LCS[0, j]。接下来,它使用所有 j 都知道 LCS[0, j] 的事实来计算所有 j 的 LCS[1, j]。然后它使用所有 j 都知道 LCS[1, j] 的事实来计算所有 j 的 LCS[2, j],依此类推。
结果,是时候计算 LCS[i, j] 并且您的特定情况适用,该算法不需要递减 i 或 j 并递归地向下下降。它已经计算了 LCS[i-1, j-1],因此它可以读取该值并继续构建表中的其余值。
视觉上最容易看到这一点。假设您要查找字符串“canon”和“annie”的 LCS。我们从这个 2D 表开始:
A N N I E
. . . . . .
C . . . . . .
A . . . . . .
N . . . . . .
O . . . . . .
N . . . . . .
最初,我们为所有 i 和 j 设置 LCS[0, j] = LCS[i, 0] = 0:
A N N I E
0 0 0 0 0 0
C 0 . . . . .
A 0 . . . . .
N 0 . . . . .
O 0 . . . . .
N 0 . . . . .
现在,我们将逐行浏览此表,并使用原始问题中描述的重复来填写缺失的条目。遍历第一行时,我们将比较字母 C 和单词“ANNIE”中的所有字母。我们永远找不到匹配项,所以我们总是使用递归 LCS[i, j] = max(LCS[i - 1, j] + LCS[i, j - 1])。这总是评估为零,所以我们得到这个:
A N N I E
0 0 0 0 0 0
C 0 0 0 0 0 0
A 0 . . . . .
N 0 . . . . .
O 0 . . . . .
N 0 . . . . .
这是有道理的,因为该表的第一行表示字符串 C 的 LCS 的长度以及 ANNIE 的所有前缀。
在下一行中,我们将尝试查找字符串 CA 的 LCS 和 ANNIE 的所有后缀。我们考虑的第一个条目匹配 A 和 A。由于这是一个匹配,我们使用递归 LCS[i, j] = 1 + LCS[i - 1, j - 1],其计算结果为 1:
A N N I E
0 0 0 0 0 0
C 0 0 0 0 0 0
A 0 1 . . . .
N 0 . . . . .
O 0 . . . . .
N 0 . . . . .
同样,我们可以通过注意“CA”和“A”的 LCS 是长度为 1 的序列 A 来检查这一点。
这里重要的细节是我们在这里不递减 i 或 j。 我们仍然需要填写这一行的其余部分,因此我们将继续前进。
对于这一行的其余条目,我们将 A 与 ANNIE 的每个字符进行比较,发现它不匹配。因此,我们将使用递归 LCS[i, j] = max(LCS[i-1, j], LCS[i, j-1]),它总是通过从其余部分中提取 1 来评估为 1行的。这显示在这里:
A N N I E
0 0 0 0 0 0
C 0 0 0 0 0 0
A 0 1 1 1 1 1
N 0 . . . . .
O 0 . . . . .
N 0 . . . . .
继续下一行为我们提供了以下信息:
A N N I E
0 0 0 0 0 0
C 0 0 0 0 0 0
A 0 1 1 1 1 1
N 0 1 2 2 2 2
O 0 . . . . .
N 0 . . . . .
同样,这是有道理的。“CAN”和“A”的LCS就是“A”,“CAN”和“AN”的LCS就是“AN”等等。
我们可以通过表的其余部分重复此操作以找到此结果表:
A N N I E
0 0 0 0 0 0
C 0 0 0 0 0 0
A 0 1 1 1 1 1
N 0 1 2 2 2 2
O 0 1 2 2 2 2
N 0 1 2 3 3 3
我们知道 LCS 的长度为 3,这是正确的。
希望这可以帮助!