Leetcode730. 不同回文子串数

这道题的解法简直堪称天才。
给定字符串S长度在[1, 1000],字符属于集合[‘a’, ‘b’, ‘c’, ‘d’],定义回文子串为删去其中0个或更多个字符得到的回文字符串。求不同的回文子串数?(mod 1000000007)
如字符串’abcdabcdabcdabcdabcdabcdabcdabcddcbadcbadcbadcbadcbadcbadcbadcba’的回文子串数mod1000000007为104860361

如果要找出所有不重复的回文子串,那么长度为1000的字符串有4^1000-1个子串,大大超出了内存的容量。且判定是否回文花费更多的时间。直接上评论区的代码:

class Solution {
     
    public int countPalindromicSubsequences(String s) {
     
        int len = s.length();
        int[][] dp = new int[len][len];

        char[] chs = s.toCharArray();
        for(int i = 0; i < len; i++){
     
            dp[i][i] = 1;   // Consider the test case "a", "b" "c"...
        }

        for(int distance = 1; distance < len; distance++){
     
            for(int i = 0; i < len - distance; i++){
     
                int j = i + distance;
                if(chs[i] == chs[j]){
     
                    int low = i + 1;
                    int high = j - 1;

              /* Variable low and high here are used to get rid of the duplicate*/

                    while(low <= high && chs[low] != chs[j]){
     
                        low++;
                    }
                    while(low <= high && chs[high] != chs[j]){
     
                        high--;
                    }
                    if(low > high){
     
                        // consider the string from i to j is "a...a" "a...a"... where there is no character 'a' inside the leftmost and rightmost 'a'
                       /* eg:  "aba" while i = 0 and j = 2:  dp[1][1] = 1 records the palindrome{"b"}, 
                         the reason why dp[i + 1][j  - 1] * 2 counted is that we count dp[i + 1][j - 1] one time as {"b"}, 
                         and additional time as {"aba"}. The reason why 2 counted is that we also count {"a", "aa"}. 
                         So totally dp[i][j] record the palindrome: {"a", "b", "aa", "aba"}. 
                         */ 

                        dp[i][j] = dp[i + 1][j - 1] * 2 + 2;  
                    } 
                    else if(low == high){
     
                        // consider the string from i to j is "a...a...a" where there is only one character 'a' inside the leftmost and rightmost 'a'
                       /* eg:  "aaa" while i = 0 and j = 2: the dp[i + 1][j - 1] records the palindrome {"a"}.  
                         the reason why dp[i + 1][j  - 1] * 2 counted is that we count dp[i + 1][j - 1] one time as {"a"}, 
                         and additional time as {"aaa"}. the reason why 1 counted is that 
                         we also count {"aa"} that the first 'a' come from index i and the second come from index j. So totally dp[i][j] records {"a", "aa", "aaa"}
                        */
                        dp[i][j] = dp[i + 1][j - 1] * 2 + 1;  
                    }
                    else{
     
                        // consider the string from i to j is "a...a...a... a" where there are at least two character 'a' close to leftmost and rightmost 'a'
                       /* eg: "aacaa" while i = 0 and j = 4: the dp[i + 1][j - 1] records the palindrome {"a",  "c", "aa", "aca"}. 
                          the reason why dp[i + 1][j  - 1] * 2 counted is that we count dp[i + 1][j - 1] one time as {"a",  "c", "aa", "aca"}, 
                          and additional time as {"aaa",  "aca", "aaaa", "aacaa"}.  Now there is duplicate :  {"aca"}, 
                          which is removed by deduce dp[low + 1][high - 1]. So totally dp[i][j] record {"a",  "c", "aa", "aca", "aaa", "aaaa", "aacaa"}
                          */
                        dp[i][j] = dp[i + 1][j - 1] * 2 - dp[low + 1][high - 1]; 
                    }
                }
                else{
     
                    dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1];  //s.charAt(i) != s.charAt(j)
                }
                dp[i][j] = dp[i][j] < 0 ? dp[i][j] + 1000000007 : dp[i][j] % 1000000007;
            }
        }

        return dp[0][len - 1];
    }
}

以及评论区的优化:
A minor optimization, preprocess the string and keep rightNext and leftNext in two arrays of int.
For every dp[i][j], we do not have to calculate low and high which costs O(n).
The following optimization can cut the time complexity from O(n ^ 3) to O(n ^ 2)

int countPalindromicSubsequences(string S) {
     
        int n=S.size();
        long long mod=1e9+7;
        vector<vector<long long>> dp(n,vector<long long> (n,0));
        for(int i=0;i<n;i++)
            dp[i][i]=1;
        for(int len=1;len<n;len++)
        {
     
            for(int i=0;i+len<n;i++)
            {
     
                int j=i+len;
                if(S[i]==S[j])
                {
     
                    int left=i+1, right=j-1;
                    while(left<=right && S[left]!=S[i])
                        left++;
                    while(left<=right && S[right]!=S[i])
                        right--;
                    if(left>right)
                        dp[i][j]=dp[i+1][j-1]*2+2;
                    else if(left==right)
                        dp[i][j]=dp[i+1][j-1]*2+1;
                    else
                        dp[i][j]=dp[i+1][j-1]*2-dp[left+1][right-1];
                }
                else
                    dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];
                
                dp[i][j] = dp[i][j] < 0? dp[i][j] + mod:dp[i][j]% mod;
            }
        }
        return (int)dp[0][n-1];
    }

变成JAVA代码:

public int countPalindromicSubsequences(String S) {
     
        int n = S.length();
        char[] s = S.toCharArray();
        int[] lastPos = new int[4], leftNext = new int[n], rightNext = new int[n];
        for (int i = 0; i < 4; i++) {
     
            lastPos[i] = -1;
        }
        for (int i = 0; i < n; i++) {
     
            int c = s[i] - 'a';
            leftNext[i] = lastPos[c];
            lastPos[c] = i;
        }
        for (int i = 0; i < 4; i++) {
     
            lastPos[i] = -1;
        }
        for (int i = n - 1; i >= 0; i--) {
     
            int c = s[i] - 'a';
            rightNext[i] = lastPos[c];
            lastPos[c] = i;
        }
        int[][] re = new int[n][n];
        for (int i = 0; i < n - 1; i++) {
     
            re[i][i] = 1;
            re[i][i + 1] = 2;
        }
        re[n - 1][n - 1] = 1;
        for (int x = 2; x < n; x++) {
     
            for (int i = 0; i + x < n; i++) {
     
                int j = x + i, r = rightNext[i], l = leftNext[j];
                if (s[i] != s[j]) {
     
                    re[i][j] = re[i + 1][j] + re[i][j - 1] - re[i + 1][j - 1];
                }
                else {
     
                    if (r == -1 || r == j) {
     
                        re[i][j] = 2 * re[i + 1][j - 1] + 2;
                    }
                    else {
     
                        if (r == l) {
     
                            re[i][j] = 2 * re[i + 1][j - 1] + 1;
                        }
                        else {
     
                            re[i][j] = 2 * re[i + 1][j - 1] - re[r + 1][l - 1];
                        }
                    }
                }
                re[i][j] = Math.floorMod(re[i][j], 1000000007);
            }
        }
        return re[0][n - 1];
    }

这种题只有天才才能自己想出来解法

你可能感兴趣的:(Leetcode730. 不同回文子串数)