对于两个字符串,找到一个最长的公共的字符串,其实我可以实现这一个算法,找到最长的公共的字符串的长度,主要就是我不会记录到的目标字符串
状态定义
-
state[i][j]
:表示字符串str1[0..i-1]
(str1
的前i
个字符)和字符串str2[0..j-1]
(str2
的前j
个字符)的最长公共子序列的长度。
初始化
-
state[0][j] = 0
:对于任何字符串str2
,str1
的空子序列与str2
的最长公共子序列长度为0。 -
state[i][0] = 0
:对于任何字符串str1
,str2
的空子序列与str1
的最长公共子序列长度为0。
状态转移方程
对于i > 0
和j > 0
,状态转移方程如下:
- 字符相同时:
-
如果
str1.charAt(i - 1) == str2.charAt(j - 1)
,则str1
的第i-1
个字符和str2
的第j-1
个字符属于LCS的一部分。 -
state[i][j] = state[i - 1][j - 1] + 1
:这种情况下,str1
和str2
的前i
个字符和前j
个字符的最长公共子序列长度等于它们各自去掉最后一个字符后的最长公共子序列长度加1。
-
- 字符不同时:
-
如果
str1.charAt(i - 1) != str2.charAt(j - 1)
,则str1
的第i-1
个字符和str2
的第j-1
个字符不属于同一个LCS。 -
state[i][j] = Math.max(state[i - 1][j], state[i][j - 1])
:在这种情况下,我们选择不包含str1
的第i-1
个字符或str2
的第j-1
个字符的最长公共子序列,取两者中较长的一个。
-
实现的步骤:
1. 动态规划表的构建
-
状态定义:使用一个二维数组
state
,其中state[i][j]
表示字符串str1
的前i
个字符和字符串str2
的前j
个字符的最长公共子序列的长度。 -
初始化:
state[i][0]
和state[0][j]
都初始化为0,因为一个空序列与任何序列的最长公共子序列长度都是0。 -
状态转移方程:
-
如果
str1.charAt(i - 1) == str2.charAt(j - 1)
,则state[i][j] = state[i - 1][j - 1] + 1
。这意味着如果str1
的第i-1
个字符和str2
的第j-1
个字符相同,那么这两个字符可以构成一个更长的公共子序列。 -
如果
str1.charAt(i - 1) != str2.charAt(j - 1)
,则state[i][j] = Math.max(state[i - 1][j], state[i][j - 1])
。这意味着如果str1
的第i-1
个字符和str2
的第j-1
个字符不同,则取不包含当前str1
字符或str2
字符的最长公共子序列。
-
2. 回溯找到最长公共子序列
- 回溯:从
state[m][n]
开始回溯,其中m
和n
分别是str1
和str2
的长度。如果str1.charAt(i - 1) == str2.charAt(j - 1)
,则这两个字符属于LCS,将它们添加到结果中,并向左上角移动(即i--
和j--
)。如果不相等,则根据state[i - 1][j]
和state[i][j - 1]
的值决定向上还是向左移动。
这段代码的时间复杂度是O(m * n),其中m和n分别是两个输入字符串的长度。空间复杂度也是O(m * n),因为需要一个二维数组来存储中间结果。这种方法是解决最长公共子序列问题的标准动态规划方法,既高效又易于理解。
package Dp;
//实现一个最长的公共子序列的计算,要求给出两个源字符串,得到一个最长公共字符串
public class LCS {
//动态规划实现
public static String get(String str1, String str2){
int m = str1.length();
int n = str2.length();
//建立状态转换表
int[][] state = new int[m + 1][n + 1];
for(int i = 0; i <= m; i++){
for (int j = 0; j <= n; j++) {
//初始化
if(i == 0 || j == 0){
state[i][j] = 0;
} else if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
state[i][j] = state[i - 1][j - 1] + 1;
} else {//两个字符不相等
state[i][j] = Math.max(state[i - 1][j], state[i][j - 1]);
}
}
}
//从状态数组state回溯找到最长公共子序列
int i = m, j = n;
StringBuilder result = new StringBuilder();
while (i > 0 && j > 0) {
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
result.append(str1.charAt(i - 1));
i--;
j--;
} else if (state[i - 1][j] > state[i][j - 1]) {
i--;
} else {
j--;
}
}
return result.reverse().toString();
}
public static void main(String[] args) {
String str1 = "ABCFR";
String str2 = "BCRF";
System.out.println(get(str1,str2));
}
}
思路是很简单的,需要注意的是代码实现时的细节,很容易被忽略,选择合适的数据结构