Given two strings s1, s2
, find the lowest ASCII sum of deleted characters to make two strings equal.
Example 1:
Input: s1 = "sea", s2 = "eat" Output: 231 Explanation: Deleting "s" from "sea" adds the ASCII value of "s" (115) to the sum. Deleting "t" from "eat" adds 116 to the sum. At the end, both strings are equal, and 115 + 116 = 231 is the minimum sum possible to achieve this.
Example 2:
Input: s1 = "delete", s2 = "leet" Output: 403 Explanation: Deleting "dee" from "delete" to turn the string into "let", adds 100[d]+101[e]+101[e] to the sum. Deleting "e" from "leet" adds 101[e] to the sum. At the end, both strings are equal to "let", and the answer is 100+101+101+101 = 403. If instead we turned both strings into "lee" or "eet", we would get answers of 433 or 417, which are higher.
Note:
0 < s1.length, s2.length <= 1000
.- All elements of each string will have an ASCII value in
[97, 122]
.
這道題給了我們兩個字符串,讓我們刪除一些字符使得兩個字符串相等,我們希望刪除的字符的ASCII碼最小。這道題跟之前那道Delete Operation for Two Strings極其類似,那道題讓求刪除的最少的字符數,這道題換成了ASCII碼值。其實很多大廠的面試就是這種改動,雖然很少出原題,但是這種小范圍的改動卻是很經常的,所以當背題俠是沒有用的,必須要完全掌握了解題思想,並能舉一反三才是最重要的。看到這種玩字符串又是求極值的題,想都不要想直接上DP,我們建立一個二維數組dp,其中dp[i][j]表示字符串s1的前i個字符和字符串s2的前j個字符變相等所要刪除的字符的最小ASCII碼累加值。那么我們可以先初始化邊緣,即有一個字符串為空的話,那么另一個字符串有多少字符就要刪多少字符,才能變空字符串。所以我們初始化dp[0][j]和dp[i][0],計算方法就是上一個dp值加上對應位置的字符,有點像計算累加數組的方法,由於字符就是用ASCII表示的,所以我們不用轉int,直接累加就可以。這里我們把dp[i][0]的計算放入大的循環中計算,是為了少寫一個for循環。好,現在我們來看遞推公式,需要遍歷這個二維數組的每一個位置即dp[i][j],當對應位置的字符相等時,s1[i-1] == s2[j-1],(注意由於dp數組的i和j是從1開始的,所以字符串中要減1),那么我們直接賦值為上一個狀態的dp值,即dp[i-1][j-1],因為已經匹配上了,不用刪除字符。如果s1[i-1] != s2[j-1],那么就有兩種情況,我們可以刪除s[i-1]的字符,且加上被刪除的字符的ASCII碼到上一個狀態的dp值中,即dp[i-1][j] + s1[i-1],或者刪除s[j-1]的字符,且加上被刪除的字符的ASCII碼到上一個狀態的dp值中,即dp[i][j-1] + s2[j-1]。這不難理解吧,比如sea和eat,當首字符s和e失配了,那么有兩種情況,要么刪掉s,用ea和eat繼續匹配,或者刪掉e,用sea和at繼續匹配,記住刪掉的字符一定要累加到dp值中才行,參見代碼如下:
解法一:
class Solution { public: int minimumDeleteSum(string s1, string s2) { int m = s1.size(), n = s2.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0)); for (int j = 1; j <= n; ++j) dp[0][j] = dp[0][j - 1] + s2[j - 1]; for (int i = 1; i <= m; ++i) { dp[i][0] = dp[i - 1][0] + s1[i - 1]; for (int j = 1; j <= n; ++j) { dp[i][j] = (s1[i - 1] == s2[j - 1]) ? dp[i - 1][j - 1] : min(dp[i - 1][j] + s1[i - 1], dp[i][j - 1] + s2[j - 1]); } } return dp[m][n]; } };
我們也可以優化空間復雜度,使用一個一維數組dp,其中dp[i]表示字符串s1和字符串s2的前i個字符變相等所要刪除的字符的最小ASCII碼累加值。剛開始還是要初始化dp[j],這里用變量t1和t2保存上一個狀態的值,並不斷更新。如果面試官沒有特別的要求,還是用二維dp數組吧,畢竟邏輯更清晰一些,一維的容易寫錯~
解法二:
class Solution { public: int minimumDeleteSum(string s1, string s2) { int m = s1.size(), n = s2.size(); vector<int> dp(n + 1, 0); for (int j = 1; j <= n; ++j) dp[j] = dp[j - 1] + s2[j - 1]; for (int i = 1; i <= m; ++i) { int t1 = dp[0]; dp[0] += s1[i - 1]; for (int j = 1; j <= n; ++j) { int t2 = dp[j]; dp[j] = (s1[i - 1] == s2[j - 1]) ? t1 : min(dp[j] + s1[i - 1], dp[j - 1] + s2[j - 1]); t1 = t2; } } return dp[n]; } };
下面這種方法思路也很巧妙,反其道而行之,相當於先計算了字符串s1和s2的最大相同子序列,在這道題中就是最大相同子序列的ASCII碼值,然后用s1和s2的所有字符之和減去這個最大ASCII碼值的兩倍,就是要刪除的字符的最小ASCII碼值了。那么還是建立二維數組dp,其中dp[i][j]表示字符串s1的前i個字符和字符串s2點前j個字符中的最大相同子序列的ASCII值。然后我們遍歷所有的位置,當對應位置的字符相等時,s1[i-1] == s2[j-1],那么我們直接賦值為上一個狀態的dp值加上這個相同的字符,即dp[i-1][j-1] + s1[i-1],注意這里跟解法一不同之處,因為dp的定義不同,所以寫法不同。如果s1[i-1] != s2[j-1],那么就有兩種情況,我們可以刪除s[i-1]的字符,即dp[i-1][j],或者刪除s[j-1]的字符,即dp[i][j-1],取二者中最大值賦給dp[i][j]。最后分別算出s1和s2的累加值,減去兩倍的dp最大值即可,參見代碼如下:
解法三:
class Solution { public: int minimumDeleteSum(string s1, string s2) { int m = s1.size(), n = s2.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0)); for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + s1[i - 1]; else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } } int sum1 = accumulate(s1.begin(), s1.end(), 0); int sum2 = accumulate(s2.begin(), s2.end(), 0); return sum1 + sum2 - 2 * dp[m][n]; } };
類似題目:
參考資料:
https://discuss.leetcode.com/topic/107995/concise-dp-solution
https://discuss.leetcode.com/topic/107980/c-dp-with-explanation
https://discuss.leetcode.com/topic/108029/lcs-variation-solution-python-c