hot100 最长公共子序列(1143)
2026/7/28 21:40:30 网站建设 项目流程

本题采用二维动态规划 (2D Dynamic Programming)算法解决双字符串最长公共子序列 (LCS, Longest Common Subsequence) 的求解问题。其核心本质是将两个序列的全局拓扑匹配问题,拆解为二维状态空间阵列中的自底向上一维步进收敛模型

通过构建大小为(m + 1) * (n + 1)的状态转移矩阵f,利用字符串索引退一的偏移技巧(1-based Index Padding),将边界初始化与状态递推统一处理。当前给出的标准解法实现了在时间复杂度 O(m * n)空间复杂度 O(m * n)条件下的全局最优求解。进一步地,由于状态转移仅依赖于当前行与上一行、左侧以及左上角单节点的状态,通过一维滚动数组(Rolling Array)可将物理空间复杂度压缩至O(min(m, n));借助Hirschberg 分治算法更能在保持线性空间 O(m + n) 的同时完整还原出具体的公共子序列路径。

一、 问题本质与拓扑状态空间模型拆解

1.1 子序列与子串的拓扑结构差异

在字符串匹配算法中,子序列 (Subsequence)子串 (Substring)存在着根本性的拓扑几何约束差异:

  • 子串 (Substring):要求字符在原字符串中处于连续物理空间。例如对于字符串"abcde""bcd"是合法子串,而"ace"不是。

  • 子序列 (Subsequence):仅要求字符在原字符串中保持相对先后顺序(单调递增索引拓扑),不要求连续物理占用。例如对于"abcde",满足index('a') < index('c') < index('e')(即0 < 2 < 4),因此"ace"是合法的子序列。

两者的解空间规模与状态转移方程有本质区别:子串匹配可以通过滑动窗口或 KMP 算法达到线性或近线性扫描;而最长公共子序列 (LCS) 包含非连续的字符跳转决策,两个长度分别为 m 和 n 的字符串,其可能的子序列数量分别为2^m2^n。直接寻找公共子序列的候选空间规模为O(2^m * 2^n),属于典型的高维组合爆炸问题。

1.2 最优子结构与重叠子问题证明

要使用动态规划解决 LCS 问题,必须证明该问题具备最优子结构 (Optimal Substructure)重叠子问题 (Overlapping Subproblems)两个核心数学性质。

1. 最优子结构性质证明

设字符串text1的长度为 m,text2的长度为 n。定义LCS(text1[0...m-1], text2[0...n-1])为两串的最长公共子序列。

考虑两字符串的末尾字符text1[m-1]text2[n-1]

  • 情形 1:末尾字符相同(text1[m-1] == text2[n-1]

    • 定理:两字符串的最后一个字符必然可以作为某个最长公共子序列的末尾元素。

    • 反证法:假设存在一个最长公共子序列 Z 不包含末尾相同的字符X = text1[m-1] = text2[n-1]。那么我们将字符 X 追加到 Z 的末尾,得到一个新的公共子序列 Z' = Z + X。Z' 的长度为|Z| + 1,比假设的最长公共子序列 Z 还要长,这与 Z 是最长公共子序列的前提矛盾。因此,末尾字符必在 LCS 中。

    • 递推归约LCS(text1[0...m-1], text2[0...n-1]) = LCS(text1[0...m-2], text2[0...n-2]) + 1。问题被转化为求解规模为(m-1, n-1)的子问题。

  • 情形 2:末尾字符不同(text1[m-1] != text2[n-1]

    • 定理:末尾字符text1[m-1]text2[n-1]不可能同时出现在同一个 LCS 中(因为如果同时出现,要求它们位于最长公共子序列的同一末尾位置,但它们的字符值不相等,矛盾)。

    • 结论:LCS 的末尾组合只有三种可能:不包含text1[m-1]、不包含text2[n-1],或者两者都不包含。

    • 递推归约

      1. 不包含text1[m-1]时,LCS 等于LCS(text1[0...m-2], text2[0...n-1])

      2. 不包含text2[n-1]时,LCS 等于LCS(text1[0...m-1], text2[0...n-2])

      3. 两者都不包含时,LCS 等于LCS(text1[0...m-2], text2[0...n-2])

    • 由于子问题 1 和子问题 2 的搜索空间均包含了子问题 3,因此求最大值时子问题 3 被隐式覆盖。故取前两者的最大值即可:

      LCS(text1[0...m-1], text2[0...n-1]) = max(LCS(text1[0...m-2], text2[0...n-1]), LCS(text1[0...m-1], text2[0...n-2]))

2. 重叠子问题可视化

在自顶向下的递归求解树中,子问题(i, j)表示求解text1[0...i-1]text2[0...j-1]的 LCS。

text1 = "abc",text2 = "ace"为例:

(3, 3) ['c' != 'e'] / \ (2, 3) ['b' != 'e'] (3, 2) ['c' != 'c'] / \ (1, 3) ['a' != 'e'] (2, 2) ['b' != 'c'] <--- 重叠节点 / \ / \ (0, 3) (1, 2) (1, 2) <--- 重叠节点 (2, 1)

可以看到,状态(1, 2)被重复计算了多次。对于长为 m, n 的字符串,状态空间的总大小仅为O(m * n),但未经记忆化的递归搜索树节点数量会爆发至O(2^(m+n))。因此,采用网格化动态规划记账法,可将指数级复杂度直接压缩为多项式级O(m * n)

二、 算法演进脉络与多维解法对比

在解决最长公共子序列问题时,算法演进经历了一维暴力搜索、自顶向下记忆化搜索、自底向上标准二维动态规划、一维滚动数组空间优化,直至线性空间路径还原算法(Hirschberg 算法)。

2.1 各主流解法时空复杂度与特性对比

解法名称时间复杂度空间复杂度核心机制优势与物理瓶颈
暴力递归 (Brute Force)O(2^(m+n))O(m + n)穷举所有子序列组合进行匹配空间小但时间爆破,对于 m, n >= 30 无法运行
记忆化搜索 (Top-Down DFS + Cache)O(m * n)O(m * n)递归深入 + 动态规划哈希/数组记忆逻辑符合直觉,按需计算;但存在递归栈开销,易发生 StackOverflow
标准二维 DP (当前源码解法)O(m * n)O(m * n)迭代填充(m+1)*(n+1)状态阵列状态转移简单,无栈溢出风险,但内存占用为双线性乘积
滚动数组空间优化 (1D/2D Rolling DP)O(m * n)O(min(m, n))利用按模复用或一维数组逆向/正向覆盖空间利用率极高,仅存储两行状态;缺点是丢失完整轨迹,无法还原 LCS 路径
Hirschberg 分治算法O(m * n)O(m + n)结合分治法与正反向 Rolling DP达成时间与空间的双重理论极限,支持在 O(m+n) 空间下还原具体字符串

三、 核心数学推导与状态转移矩阵构建

3.1 状态定义与偏移 Padding 机制

定义二维状态数组f[i][j]

f[i][j]表示字符串text1的前i个字符(即text1[0...i-1])与字符串text2的前j个字符(即text2[0...j-1])的最长公共子序列的长度。

为什么需要 1-based Padding(偏移 Padding)?

在代码实现中,开辟大小为(m + 1) * (n + 1)的二维数组f,而不是m * n

字符串 text1: t e x t 1 [索引 0...m-1] DP 状态索引 i: 0 1 2 3 4 5 [代表前 i 个字符]
  • 物理边界表达i = 0表示text1为空字符串""j = 0表示text2为空字符串""。任何字符串与空字符串的最长公共子序列长度均为0

  • 消除越界判断:如果直接使用 0-based 映射(f[i][j]表示text1[i]text2[j]),当处于i = 0j = 0边界时,公式中的f[i-1][j-1]会访问f[-1][-1],需要编写大量额外的if (i > 0 && j > 0)分支。

  • 统一递推形式:通过偏移,text1的第i个字符在数组中的索引为text1.charAt(i - 1)(源码中通过外层循环i从 0 到m-1,内层f[i+1][j+1]隐式实现了这一 Padding 对齐)。

3.2 完整状态转移方程

根据前文的最优子结构推导,得到严格的数学递推式:

/ 0 , 当 i = 0 或 j = 0 f[i][j] = | f[i-1][j-1] + 1 , 当 text1[i-1] == text2[j-1] \ max( f[i-1][j], f[i][j-1] ) , 当 text1[i-1] != text2[j-1]
分支深度解析:
  1. text1[i-1] == text2[j-1]

    当前位置的字符相同,形成了公共字符。这一新的对齐字符可以将此前text1[0...i-2]text2[0...j-2]的匹配结果直接延长 1 位。此时绝对不需要去比较f[i-1][j]f[i][j-1],因为f[i-1][j-1] + 1在数学上严格大于等于前两者。

  2. text1[i-1] != text2[j-1]

    当前位置字符不匹配,因此这两个字符不可能同时包含在最新的公共子序列中。此时最长公共子序列只能来源于以下两种放弃方案的最大者:

    • 放弃text1的最后一个字符text1[i-1],继承f[i-1][j]的状态。

    • 放弃text2的最后一个字符text2[j-1],继承f[i][j-1]的状态。

四、 算法执行状态机步进推演与图解

为了完整展现算法的底层演算逻辑,以示例 1为例进行全状态机推演:

  • 输入:text1 = "abcde"(长度 m = 5)

  • 输入:text2 = "ace"(长度 n = 3)

  • 状态矩阵尺寸:6 * 4

4.1 状态转移矩阵全量演进网格 (f[i][j])

下表展示了循环结束后的最终 DP 矩阵(行代表text1的前 i 个字符,列代表text2的前 j 个字符):

f[i][j]空串 (j=0)'a' (j=1)'c' (j=2)'e' (j=3)
空串 (i=0)0000
'a' (i=1)01(对齐)11
'b' (i=2)0111
'c' (i=3)012(对齐)2
'd' (i=4)0122
'e' (i=5)0123(对齐)

4.2 逐步计算执行轨迹

下面详述外层循环i(从 0 到 4)与内层循环j(从 0 到 2)的单步执行流:

  1. 外层 i = 0 (text1[0] = 'a'):

    • j = 0 ('a'):'a' == 'a'。匹配成功!f[1][1] = f[0][0] + 1 = 0 + 1 = 1

    • j = 1 ('c'):'a' != 'c'。不匹配。f[1][2] = max(f[0][2], f[1][1]) = max(0, 1) = 1

    • j = 2 ('e'):'a' != 'e'。不匹配。f[1][3] = max(f[0][3], f[1][2]) = max(0, 1) = 1

  2. 外层 i = 1 (text1[1] = 'b'):

    • j = 0 ('a'):'b' != 'a'f[2][1] = max(f[1][1], f[2][0]) = max(1, 0) = 1

    • j = 1 ('c'):'b' != 'c'f[2][2] = max(f[1][2], f[2][1]) = max(1, 1) = 1

    • j = 2 ('e'):'b' != 'e'f[2][3] = max(f[1][3], f[2][2]) = max(1, 1) = 1

  3. 外层 i = 2 (text1[2] = 'c'):

    • j = 0 ('a'):'c' != 'a'f[3][1] = max(f[2][1], f[3][0]) = max(1, 0) = 1

    • j = 1 ('c'):'c' == 'c'。匹配成功!f[3][2] = f[2][1] + 1 = 1 + 1 = 2

    • j = 2 ('e'):'c' != 'e'f[3][3] = max(f[2][3], f[3][2]) = max(1, 2) = 2

  4. 外层 i = 3 (text1[3] = 'd'):

    • j = 0 ('a'):'d' != 'a'f[4][1] = max(f[3][1], f[4][0]) = max(1, 0) = 1

    • j = 1 ('c'):'d' != 'c'f[4][2] = max(f[3][2], f[4][1]) = max(2, 1) = 2

    • j = 2 ('e'):'d' != 'e'f[4][3] = max(f[3][3], f[4][2]) = max(2, 2) = 2

  5. 外层 i = 4 (text1[4] = 'e'):

    • j = 0 ('a'):'e' != 'a'f[5][1] = max(f[4][1], f[5][0]) = max(1, 0) = 1

    • j = 1 ('c'):'e' != 'c'f[5][2] = max(f[4][2], f[5][1]) = max(2, 1) = 2

    • j = 2 ('e'):'e' == 'e'。匹配成功!f[5][3] = f[4][2] + 1 = 2 + 1 = 3

最终返回f[5][3] = 3

4.3 状态追溯与路径还原拓扑 (Backtracking Path)

从最终状态f[m][n]开始向左上角反向回溯,可以完整还原出具体的 LCS 字符串:

(5, 3) [val=3, 'e'=='e'] ==> 选定字符 'e',向左上角移动至 (4, 2) | (4, 2) [val=2, 'd'!='c'] ==> 上方 f[3][2]=2, 左方 f[4][1]=1,向上方移动至 (3, 2) | (3, 2) [val=2, 'c'=='c'] ==> 选定字符 'c',向左上角移动至 (2, 1) | (2, 1) [val=1, 'b'!='a'] ==> 上方 f[1][1]=1, 左方 f[2][0]=0,向上方移动至 (1, 1) | (1, 1) [val=1, 'a'=='a'] ==> 选定字符 'a',向左上角移动至 (0, 0) | (0, 0) [到达终点]

将选定的字符逆序排列:['e', 'c', 'a']->"ace",即得到原问题的最长公共子序列实体。

五、 源码实现与逐行深度剖析

以下为题干给出的 Java 源码及关键位置注释:

class Solution { public int longestCommonSubsequence(String text1, String text2) { // 1. 获取两字符串的物理长度 int m = text1.length(); int n = text2.length(); // 2. 构建二维 DP 状态数组,+1 维度用于处理空字符串基准边界 Padding int[][] f = new int[m + 1][n + 1]; // 3. 双重循环遍历两个字符串的所有字符组合 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 4. 当字符匹配成功时,触发左上角对角线转移 if (text1.charAt(i) == text2.charAt(j)) { f[i + 1][j + 1] = f[i][j] + 1; } // 5. 当字符不匹配时,取上侧与左侧状态的最大值 else { f[i + 1][j + 1] = Math.max(f[i + 1][j], f[i][j + 1]); } } } // 6. 返回全局覆盖下的最长公共子序列长度 return f[m][n]; } }

关键语义剖析:

  • f[i + 1][j + 1]对应的是当前外层索引i和内层索引j所涵盖的前i + 1和前j + 1个字符。

  • text1.charAt(i)text2.charAt(j)的比较,实际上是处理f[i + 1][j + 1]对应的末尾字符。

  • f[i][j]对应左上角对角线元素(即消去这两个匹配字符后的前缀匹配结果)。

六、 复杂度分析与硬件级性能优化

6.1 渐进复杂度分析

  • 时间复杂度:O(m * n)

    • 外层循环迭代m次,内层循环迭代n次。

    • 循环体内的字符提取charAt()、相等性判定以及Math.max()均为常数时间操作O(1)

    • 总基本操作次数为m * n,时间复杂度严格呈O(m * n)。对于题目限制m, n <= 1000,总计算次数约为10^6次,完全可以在 10ms 数量级内运行完毕。

  • 空间复杂度:O(m * n)

    • 主要消耗来自二维数组f的物理内存分配。

    • 分配了(m + 1) * (n + 1)int类型元素,占用字节数为(m + 1) * (n + 1) * 4字节。

    • m = 1000, n = 1000时,空间开销约为1000 * 1000 * 4 bytes ≈ 4 MB,在现代计算机内存中完全处于可接受范围。

6.2 现代计算机硬件架构视角下的性能表现

虽然该算法在数学时间复杂度上已达标,但在工业级高性能应用场景下,底层系统架构仍有若干物理性能影响因素:

1. JVM 内存布局与二维数组寻址开销

在 Java 中,二维数组int[][]并不是连续的二维平面内存,而是“数组的数组(Array of Arrays)”:

f 引用 -> [指针 row0, 指针 row1, 指针 row2, ..., 指针 rowM] | v [int0, int1, ..., intN] (连续堆内存)
  • 寻址开销:访问f[i + 1][j + 1]涉及两次内存间接寻址(先取行指针,再计算列偏移)。

  • 缓存行失效(Cache Line Miss):由于外层循环按行推进,内层按列推进(j变化最快),行内元素在内存中是严格连续的。这种行主序(Row-Major)访问符合 CPU L1/L2 Cache 的预取指令(Data Prefetching)规范,使得 Cache Line 命中率极高。若倒转循环顺序(外层j,内层i),将导致严重的 CPU 缓存失效。

2.String.charAt()边界检查惩罚

在 Java 的String.charAt(i)源码中:

public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return isLatin1() ? StringLatin1.charAt(value, index) : StringUTF16.charAt(value, index); }

每次调用charAt()均包含隐式的区间边界检查和编码格式分支判定。尽管 JIT 编译器(C2 Compiler)会尝试进行消除边界检查 (Bounds Check Elimination)优化,但在双重循环体内频繁调用仍存在额外指令开销。

工业级优化技巧:在循环前将String显式转换为原生字符数组char[]

char[] s1 = text1.toCharArray(); char[] s2 = text2.toCharArray();

这一操作将原本内层循环中每秒数亿次的非连续方法调用,转换为基于基址指针直接进行内存偏移读取,能带来20% ~ 40% 的运行速度提升

七、 算法演进:空间极致优化与变体延伸

在实际工程场景中,如处理 DNA 基因序列匹配(m, n可能达到10^510^6),O(m * n)的空间复杂度会瞬间导致内存溢出 (OOM)。因此必须对空间复杂度进行演进优化。

7.1 一维滚动数组空间优化 (Space-Optimized DP)

观察状态转移方程:

f[i+1][j+1]的计算仅依赖于:

  1. 当前行的前一个状态:f[i+1][j](左侧)

  2. 上一行的同列状态:f[i][j+1](上侧)

  3. 上一行的前一列状态:f[i][j](左上角)

这表明计算第i + 1行时,历史中第0到第i - 1行的状态已完全失效。因此仅需维护两行状态,甚至可以压缩至单行状态数组

一维滚动数组代码实现 (Java):
class SolutionOptimized { public int longestCommonSubsequence(String text1, String text2) { // 保证 text2 为较短的字符串,将空间复杂度进一步压低至 O(min(m, n)) if (text1.length() < text2.length()) { return longestCommonSubsequence(text2, text1); } char[] s1 = text1.toCharArray(); char[] s2 = text2.toCharArray(); int m = s1.length; int n = s2.length; // 仅维护一层一维数组,存储当前迭代行的DP值 int[] dp = new int[n + 1]; for (int i = 0; i < m; i++) { // pre 用于暂存左上角对角线元素 f[i][j] 的值 int pre = 0; for (int j = 0; j < n; j++) { int temp = dp[j + 1]; // 暂存未覆盖前的 dp[j+1],即上一行的 f[i][j+1] (上侧) if (s1[i] == s2[j]) { dp[j + 1] = pre + 1; // pre 即为上一行的 f[i][j] (左上角) } else { dp[j + 1] = Math.max(dp[j + 1], dp[j]); // dp[j+1]为上侧,dp[j]为左侧 } pre = temp; // 更新 pre,为下一次迭代的左上角提供数据 } } return dp[n]; } }
  • 复杂度改善

    • 时间复杂度:仍保持O(m * n)

    • 空间复杂度:直接下降至O(min(m, n))。若字符串长度为 1000,数组大小仅需 1000 个int(约 4 KB)。

7.2 Hirschberg 分治算法(线性空间还原路径)

一维滚动数组虽然将空间降到了 O(min(m, n)),但丢失了回溯所需的全局矩阵,导致无法还原 LCS 字符串。Hirschberg 算法结合了动态规划分治法 (Divide and Conquer),实现了在O(m * n) 时间复杂度O(m + n) 空间复杂度下还原出完整字符串。

算法核心原理:
  1. 切分:将text1从中间位置mid = m / 2划分为左右两半。

  2. 双向 DP

    • 在正向字符串上运行 O(N) 空间的 Rolling DP,计算出前半段与text2各前缀的 LCS 长度。

    • 在反向字符串(反转串)上运行 Rolling DP,计算出后向子串与text2各后缀的 LCS 长度。

  3. 拼合:寻找一个切分点k0 <= k <= n),使得Forward_DP[k] + Backward_DP[n - k]达到最大。这个k即为 LCS 在text2中的最佳切分边界。

  4. 递归:对左半部分(text1[0...mid], text2[0...k])和右半部分(text1[mid...m], text2[k...n])分别递归求解,并将结果拼接。

由于递归深度为O(log m),每一层的计算总量构成收敛几何级数:m*n + (m/2)*n + (m/4)*n + ... = 2 * m * n,因此总时间复杂度依然是O(m * n),而空间复杂度仅为递归栈空间O(m + n)

7.3 相关经典衍生题型关系拓扑

最长公共子序列 (LCS) 是序列匹配类动态规划的基石,许多经典算法问题均可规约(Reduce)至 LCS 范畴:

最长公共子序列 (LCS) | +------------------------+------------------------+ | | | v v v 编辑距离 (Edit Distance) 最长回文子序列 (LPS) 最短公共超序列 (SCS) (LeetCode 72) (LeetCode 516) (LeetCode 1092)
  1. 最长回文子序列 (LPS)

    求字符串S的最长回文子序列,等价于求解S与其反转字符串S' = reverse(S)LCS

    LPS(S) = LCS(S, reverse(S))

  2. 字符串的最小删除/编辑距离 (Edit Distance)

    text1转换为text2所需的最少插入/删除操作次数。

    若仅允许删除操作,最小删除次数为:

    Min_Deletions = text1.length() + text2.length() - 2 * LCS(text1, text2)

  3. 最短公共超序列 (SCS)

    构造一个最短的字符串S,使得text1text2均为S的子序列。

    SCS_Length = text1.length() + text2.length() - LCS(text1, text2)

八、 工业级应用场景实战

最长公共子序列算法不仅在算法竞赛中高频出现,更构成了现代软件工程基础设施的核心算法底层:

8.1 Git Diff 与版本控制系统

在软件开发中,git diff指令用于对比两个文本文件的差异。

  • 物理抽象:Git 将文件内容按拆分为字符串数组(每行文本视为一个元素)。

  • 求解核心:文件 A 与文件 B 的git diff,其本质就是求解这两个行数组的最长公共子序列 (LCS)

  • 差异渲染:属于 LCS 中的行被标记为无变更(保留);仅存在于文件 A 的行被标记为删除(-);仅存在于文件 B 的行被标记为新增(+)。

  • 工业优化:由于源代码文件通常很长,Git 实际采用了基于 LCS 的Myers 差分算法(结合了贪心与图搜索),在稀疏变更场景下能达到近乎线性的执行效率。

8.2 生物信息学中的 DNA / 蛋白质序列比对

在基因组学研究中,DNA 序列由四种碱基(A, T, C, G)组成。

  • 基因同源性分析:为了判断不同物种之间是否存在进化上的亲缘关系,科学家需要比对两段 DNA 序列的相似度。

  • 全局与局部比对算法:基于 LCS 算法思想衍生出的Needleman-Wunsch 算法(全局比对)与Smith-Waterman 算法(局部比对),通过在 DP 状态转移方程中加入突变惩罚(Mismatch Penalty)和空位惩罚(Gap Penalty),成为了现代基因比对软件(如 BLAST)的底层数学基石。

8.3 文本相似度与防抄袭检测系统

在学术论文查重、文档聚类及搜索引擎中:

  • 通过计算两篇文档词汇序列的 LCS 长度,并结合 Jaccard 相似度系数或余弦相似度:

    SimilarityRatio = LCS(DocA, DocB) / min(Length(DocA), Length(DocB))

  • 该指标能有效免疫“语序微调”、“填充无关修饰词”等抄袭手段,精确捕捉文章的主干逻辑结构。

九、 全文总结与工程实践避坑指南

9.1 动态规划解题模板归纳

解决双字符串 / 双数组匹配类问题的通用步骤:

  1. 定义二维矩阵:开辟(m + 1) * (n + 1)尺寸的 DP 数组,统一处理空串基准边界。

  2. 明确对齐条件:若末尾元素匹配成功,优先走左上角对角线状态转移(+1);若不匹配,走上侧与左侧的聚合作业(max/min)。

  3. 循环扫描与空间压缩:先编写可读性最高且便于调试的 2D DP 源码;若遭遇严格空间限制,引入一维变量暂存pre转换至滚动数组。

9.2 常见踩坑点(Common Pitfalls)

  • 索引错位(Off-by-One Error):在 DP 数组定义为f[m+1][n+1]的情况下,访问原字符串必须取text1.charAt(i - 1)。源码中若使用i从 0 到m-1递增,在赋值时写成f[i+1][j+1],其对应的字符串字符即为charAt(i),必须保持两端索引映射的严密对应。

  • 初始化遗漏:虽然 Java 默认将new int[][]元素初始化为0,满足了空串匹配为 0 的性质,但在某些带有自定义权重或惩罚项的变体(如编辑距离、带权 LCS)中,必须显式对f[i][0]f[0][j]进行线性累加初始化。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询