516. Longest Palindromic Subsequence
阿新 • • 發佈:2020-09-06
問題:
給定一個字串,求其中最長迴文子序列(子序列不是連續字串)的長度。
Example 1: Input: "bbbab" Output: 4 One possible longest palindromic subsequence is "bbbb". Example 2: Input: "cbbd" Output: 2 One possible longest palindromic subsequence is "bb". Constraints: 1 <= s.length <= 1000 s consists only of lowercase English letters.
解法:DP(動態規劃)
1.確定【狀態】:字串s的
- 第i個字元:s[i]
- 第j個字元:s[j]
2.確定【選擇】:分兩種情況
- s[i] == s[j]:
- 前一個子串狀態<不包含當前這兩個字元s[i]s[j]>(公共序列長度)+2: dp[i+1][j-1] + 2
- s[i] != s[j]:有以下2種情況,取最大值。
- 只有s[i]是最終最長迴文子序列的一個字元 -> =上一個包含s[i]而不包含s[j]的字元狀態:dp[i][j-1]
- 只有s[j]是最終最長迴文子序列的一個字元->=上一個不包含s[i]而包含s[j]的字元狀態:dp[i+1][j]
- ★由於dp[i+1][j-1]一定<=dp[i][j-1] or dp[i+1][j],因此可以省略比較dp[i+1][j-1]
3. dp[i][j]的含義:
字串s的第 i 個字元到第 j 個字元為止,這段子串中,存在最長迴文子序列的長度。
4. 狀態轉移:
dp[i][j]=
- (s[i] == s[j]):=前一個子串狀態+2:dp[i+1][j-1] + 2
- (s[i] != s[j]):=max {
- 上一個包含s[i]字元的狀態:dp[i][j-1]
- 上一個包含s[j]字元的狀態:dp[i+1][j]
- 上一個s[i]s[j]都不包含的狀態:dp[i+1][j-1](★可省略) }
5. base case:
- dp[i][i]=1:單文字子串,最長迴文子序列為它自己,長度為 1。
6. 遍歷順序:
根據狀態轉移公式,在求得dp[i][j]之前,需先求得dp[i+1][j-1],dp[i+1][j],dp[i][j-1]
因此需要:i:大->小,j:小->大 遍歷
⚠️ 注意:本問題,i一定<=j,因此dp[i][j]中i>j的memory基本不會被用到。
只有在base case計算dp[i][i+1]且s[i]==s[i+1]的時候,作為★dp[i+1][j-1]被用到。應該為0。這也是在後面♻️ 優化處pre的賦值理由。
程式碼參考:
1 class Solution { 2 public: 3 //dp[i][j]: in substring: s[i~j], the length of LPS. 4 //case_1: s[i]==s[j]:dp[i+1][j-1] + 2 5 // add 2 to the pre status(which both not include s[i]s[j]) dp[i+1][j-1] 6 //case_2: s[i]!=s[j]: max of following 2 case: 7 // case_2_1: the pre status(only include s[i],s[i] is in LPS). dp[i][j-1] 8 // case_2_2: the pre status(only include s[j],s[j] is in LPS). dp[i+1][j] 9 //base case: 10 //dp[i][i]:1 11 int longestPalindromeSubseq(string s) { 12 int n = s.length(); 13 vector<vector<int>> dp(n, vector<int>(n, 0)); 14 for(int i=0; i<n; i++) { 15 dp[i][i] = 1; 16 } 17 for(int i=n-1; i>=0; i--) { 18 for(int j=i+1; j<n; j++) { 19 if(s[i]==s[j]) { 20 dp[i][j] = dp[i+1][j-1] +2; 21 } else { 22 dp[i][j] = max(dp[i][j-1], dp[i+1][j]); 23 } 24 } 25 } 26 return dp[0][n-1]; 27 } 28 };
♻️ 優化:
空間複雜度:2維->1維
去掉 i
壓縮所有行到一行。
左下角dp[i+1][j-1]會被上面的dp[i][j-1]覆蓋,因此引入變數pre,在更新dp[i][j-1]之前,儲存dp[i+1][j-1]
程式碼參考:
1 class Solution { 2 public: 3 int longestPalindromeSubseq(string s) { 4 int n = s.length(); 5 vector<int> dp(n, 0); 6 for(int i=n-1; i>=0; i--) { 7 int pre = 0; 8 //base case:for s[i~i+1]->e.g.'aa',s[i]=s[j]->dp[j]=pre+2 (should)=2->pre=0 9 dp[i] = 1;//base case 10 for(int j=i+1; j<n; j++) { 11 int tmp = dp[j]; 12 if(s[i]==s[j]) { 13 dp[j] = pre +2; 14 } else { 15 dp[j] = max(dp[j-1], dp[j]); 16 } 17 pre = tmp; 18 } 19 } 20 return dp[n-1]; 21 } 22 };