img的srcset屬性的作用
最長公共子序列類問題
最長公共子序列
給定兩個字串 text1 和 text2,返回這兩個字串的最長公共子序列的長度。如果不存在公共子序列,返回 0 。
一個字串的 子序列 是指這樣一個新的字串:它是由原字串在不改變字元的相對順序的情況下刪除某些字元(也可以不刪除任何字元)後組成的新字串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
兩個字串的 公共子序列 是這兩個字串所共同擁有的子序列。
備忘錄法
// 備忘錄,消除重疊子問題 int[][] memo; /* 主函式 */ int longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); // 備忘錄值為 -1 代表未曾計算 memo = new int[m][n]; for (int[] row : memo) Arrays.fill(row, -1); // 計算 s1[0..] 和 s2[0..] 的 lcs 長度 return dp(s1, 0, s2, 0); } // 定義:計算 s1[i..] 和 s2[j..] 的最長公共子序列長度 int dp(String s1, int i, String s2, int j) { // base case if (i == s1.length() || j == s2.length()) { return 0; } // 如果之前計算過,則直接返回備忘錄中的答案 if (memo[i][j] != -1) { return memo[i][j]; } // 根據 s1[i] 和 s2[j] 的情況做選擇 if (s1.charAt(i) == s2.charAt(j)) { // s1[i] 和 s2[j] 必然在 lcs 中 memo[i][j] = 1 + dp(s1, i + 1, s2, j + 1); } else { // s1[i] 和 s2[j] 至少有一個不在 lcs 中 memo[i][j] = Math.max( dp(s1, i + 1, s2, j), dp(s1, i, s2, j + 1) ); } return memo[i][j]; }
ps:原本應該是
// 情況一、s1[i] 不在 lcs 中
dp(s1, i + 1, s2, j),
// 情況二、s2[j] 不在 lcs 中
dp(s1, i, s2, j + 1),
// 情況三、都不在 lcs 中
dp(s1, i + 1, s2, j + 1)
有一個小的優化,情況三「s1[i]
和 s2[j]
都不在 lcs 中」其實可以直接忽略。
因為我們在求最大值嘛,情況三在計算 s1[i+1..]
和 s2[j+1..]
的 lcs
長度,這個長度肯定是小於等於情況二 s1[i..]
和 s2[j+1..]
中的 lcs
長度的,因為 s1[i+1..]
比 s1[i..]
短嘛,那從這裡面算出的 lcs
同理,情況三的結果肯定也小於等於情況一。說白了,情況三被情況一和情況二包含了,所以我們可以直接忽略掉情況三
dp陣列法
int longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); int[][] dp = new int[m + 1][n + 1]; // 定義:s1[0..i-1] 和 s2[0..j-1] 的 lcs 長度為 dp[i][j] // 目標:s1[0..m-1] 和 s2[0..n-1] 的 lcs 長度,即 dp[m][n] // base case: dp[0][..] = dp[..][0] = 0 for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // 現在 i 和 j 從 1 開始,所以要減一 if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一個不在 lcs 中 dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; }
字串的刪除操作
給定兩個單詞
word1
和word2
,返回使得word1
和word2
相同所需的最小步數。每步 可以刪除任意一個字串中的一個字元。
題目讓我們計算將兩個字串變得相同的最少刪除次數,那我們可以思考一下,最後這兩個字串會被刪成什麼樣子?
刪除的結果不就是它倆的最長公共子序列嘛!
public int minDistance(String word1, String word2) {
int lcs=longestCommonSubsequence(word1,word2);
return word1.length()-lcs+word2.length()-lcs;
}
最小 ASCII 刪除和
// 備忘錄
int memo[][];
/* 主函式 */
int minimumDeleteSum(String s1, String s2) {
int m = s1.length(), n = s2.length();
// 備忘錄值為 -1 代表未曾計算
memo = new int[m][n];
for (int[] row : memo)
Arrays.fill(row, -1);
return dp(s1, 0, s2, 0);
}
// 定義:將 s1[i..] 和 s2[j..] 刪除成相同字串,
// 最小的 ASCII 碼之和為 dp(s1, i, s2, j)。
int dp(String s1, int i, String s2, int j) {
int res = 0;
// base case
if (i == s1.length()) {
// 如果 s1 到頭了,那麼 s2 剩下的都得刪除
for (; j < s2.length(); j++)
res += s2.charAt(j);
return res;
}
if (j == s2.length()) {
// 如果 s2 到頭了,那麼 s1 剩下的都得刪除
for (; i < s1.length(); i++)
res += s1.charAt(i);
return res;
}
if (memo[i][j] != -1) {
return memo[i][j];
}
if (s1.charAt(i) == s2.charAt(j)) {
// s1[i] 和 s2[j] 都是在 lcs 中的,不用刪除
memo[i][j] = dp(s1, i + 1, s2, j + 1);
} else {
// s1[i] 和 s2[j] 至少有一個不在 lcs 中,刪一個
memo[i][j] = Math.min(
s1.charAt(i) + dp(s1, i + 1, s2, j),
s2.charAt(j) + dp(s1, i, s2, j + 1)
);
}
return memo[i][j];
}
base case 有一定區別,計算 lcs
長度時,如果一個字串為空,那麼 lcs
長度必然是 0;但是這道題如果一個字串為空,另一個字串必然要被全部刪除,所以需要計算另一個字串所有字元的 ASCII 碼之和。
關於狀態轉移,當 s1[i]
和 s2[j]
相同時不需要刪除,不同時需要刪除,所以可以利用 dp
函式計算兩種情況,得出最優的結果。