最長公共子字串的java語言求解及記憶體優化
阿新 • • 發佈:2019-01-02
動態規劃的本質是以空間換時間:把一個問題劃分成許多子問題,如果這些子問題會被計算很多次,那就把它們的計算結果儲存起來以節省計算時間。比如斐波那契數列的公式F(n) = F(n-1) + F(n-2)
,其中F(n-1)
和F(n-2)
的結果會被計算很多次,所以可以使用一個數組儲存對應的結果a[i] = F(i)
。
動態規劃問題求解的一般步驟是先劃分子問題、找出狀態轉移方程、找出邊界條件然後計算。在最長公共子字串這個問題中,子問題可以如此劃分:
令L(i,j)表示字串s1[0..i]和字串s2[0..j]的以s1[i]和s2[j]結尾的最長公共子字串的長度,則
1. 當s1[i] == s2[j] 時,L(i,j) = L(i-1,j-1)+1;
2. 當s1[i] != s2[j]時,L(i,j) = 0
計算出所有的L(i,j),其中0<=i<s1.length,0<=j<s2.length
,然後遍歷L
矩陣找出最大值即可。
程式碼如下,其中找出最大值這一步和計算矩陣結果這一步可以合併,以減少迴圈次數:
public static int f(String s1, String s2) {
int m = s1.length();
int n = s2.length();
char[] c1 = s1.toCharArray();
char [] c2 = s2.toCharArray();
int[][] dp = new int[m][n];
//初始化dp的第一行和第一列
for (int i = 0; i < m; i++) {
if (c1[i] == c2[0])
dp[i][0] = 1;
}
for (int i = 0; i < n; i++) {
if (c1[0] == c2[i])
dp[0][i] = 1;
}
//生成狀態轉移矩陣並找出最大值
int max = 0;
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (c1[i] == c2[j]) {
int result = dp[i - 1][j - 1] + 1;
dp[i][j] = result;
max = max < result ? result : max;
}
}
}
return max;
}
可以看到,在計算矩陣dp
的第i
行時,只用到了dp
的第i-1
行,所以只使用兩行陣列就可以計算出矩陣的值,這樣可以減少記憶體消耗。程式碼如下:
public static int fLessMem(String s1, String s2) {
int m = s1.length();
int n = s2.length();
char[] c1 = s1.toCharArray();
char[] c2 = s2.toCharArray();
int[][] dp = new int[2][n];
for (int i = 0; i < n; i++) {
if (c1[0] == c2[i]) {
dp[1][i] = 1;
}
}
int max = 0;
int cur = 1; //指明dp矩陣的哪一行是正在計算的行
int data = 0; //指明dp矩陣的行是快取的行
for (int i = 1; i < m; i++) {
//更新快取行的第一個元素
//由於現在計算的是第i行,所以data行對應的是第i-1行的元素
dp[data][0] = c1[i - 1] == c2[0] ? 1 : 0;
for (int j = 1; j < n; j++) {
if (c1[i] == c2[j]) {
int result = dp[data][j - 1] + 1;
dp[cur][j] = result;
max = max < result ? result : max;
}
}
//交換正在計算的行和快取的資料行
cur ^= data;
data ^= cur;
cur ^= data;
}
return max;
}