最长回文串

int countSubstrings(string s) {
    // 设dp[i][j]表示s[i..j]是否是回文串,0<=i<=j<N
    // dp[i][j] = s[i]==s[j] && dp[i+1][j-1]
    // dp在i维上只依赖i+1项,可省掉i维,i仍从右往左遍历
    // 要让dp[j-1]表示旧状态dp[i+1][j-1],j从右往左遍历
    const int N = s.size();
    vector<bool> dp(N, false);
    int ans = 0;
    for (int i = N - 1; i >= 0; i--) {
        for (int j = N - 1; j >= i; j--) {
            dp[j] = (s[i] == s[j]);
            if (i + 1 <= j - 1) dp[j] = dp[j] && dp[j-1];

            if (dp[j]) ans++;
        }
    }
    return ans;
}

找回文串还可以从各可能的中心往外扩展。

string longestPalindrome(string s) {
    int longest = 0, lo = 0;
    for (int i = 0; i < s.size(); i++) {
        int len1 = expand(s, i, i);
        int len2 = expand(s, i, i + 1);
        int len = max(len1, len2);
        if (len > longest) {
            longest = len;
            lo = i - (len - 1) / 2;
        }
    }
    return s.substr(lo, longest);
}

int expand(const string &s, int l, int r) {
    while (l >= 0 && r < s.size() && s[l] == s[r]) {
        l--;
        r++;
    }
    return r - l - 1; // (l,r)
}

去找S和S'(\(S_{reversed}\))的最长公共子串是错的。比如S=abacdfgdcaba、S'=abacdgfdcaba,其最长公共子串abacd并不是回文。

做最少的切割,使变成一个个回文串

回文子串的判断同上dp

int minCut(string s) {
    // 设dp[i][j]表示s[i..j]是回文串,0<=i<=j<N
    // dp[i][j] = s[i]==s[j] && dp[i+1][j-1]
    const int N = s.size();
    vector<vector<bool>> dp(N, vector<bool>(N, false));
    for (int i = N - 1; i >= 0; i--) {
        for (int j = i; j < N; j++) {
            dp[i][j] = s[i] == s[j];
            if (i + 1 <= j - 1) dp[i][j] = dp[i][j] && dp[i+1][j-1];
        }
    }

    // 设cut[i]表示子串s[0..i]的最少切割数,0<=i<N
    // dp[0][i]==true时,cut[i]=0;否则,对于0<=k<i,
    // 若满足dp[k+1][i]==true,cut[i]=min{ cut[k]+1 }
    vector<int> cut(N, INT_MAX);
    for (int i = 0; i < N; i++) {
        if (dp[0][i]) {
            cut[i] = 0;
        } else {
            for (int k = 0; k < i; k++) {
                if (dp[k+1][i]) {
                    cut[i] = min(cut[i], cut[k] + 1);
                }
            }
        }
    }
    return cut[N-1];
}

最长回文子序列

int longestPalindromeSubseq(string s) {
    // 设dp[i][j]表示s[i..j]的最长回文子序列长,0<=i<=j<N
    // 若s[i]==s[j],dp[i][j]=dp[i+1][j-1]+2,i<j
    // 否则,dp[i][j]=max(dp[i+1][j], dp[i][j-1])
    // 初始dp[i][i]=1
    const int N = s.size();
    vector<vector<int>> dp(N, vector<int>(N, 0));
    for (int i = 0; i < N; i++) {
        dp[i][i] = 1;
    }

    for (int len = 2; len <= N; len++) {
        for (int i = 0; i + len <= N; i++) {
            int j = i + len - 1;
            if (s[i] == s[j]) {
                dp[i][j] = dp[i+1][j-1] + 2;
            } else {
                dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
            }
        }
    }
    return dp[0][N-1];
}

不同回文子序列的个数

“不同”子序列,按字母划分子问题

class Solution {
    const int CHAR_CNT = 4; // 只有abcd四种字母
    const int MOD = 1e9 + 7;
public:
    int countPalindromicSubsequences(string S) {
        // 考虑某字母作最外层的区间,比如"bccb",b作最外层的区间S[firstIdx(b)..lastIdx(b)]。
        // 这最外层可贡献回文b(不再考虑内层)、bb(firstIdx(b)!=lastIdx(b))。
        // 当最外层为bb时,内层可为空集和非空子问题S[firstIdx(b)+1..lastIdx(b)-1]]。
        // 只要最外层不同就一定是不同的回文串,不用管内层是什么,计数可累加。
        const int N = S.size();
        vector<set<int>> idxSets(CHAR_CNT); // char=>idxSet
        for (int i = 0; i < N; i++) {
            idxSets[S[i] - 'a'].insert(i);
        }

        vector<vector<int>> memo(N, vector<int>(N, -1)); // S[lo..hi] => count
        return dfs(0, N - 1, idxSets, memo);
    }
    
    int dfs(int lo, int hi, vector<set<int>> &idxSets, vector<vector<int>> &memo) {
        if (lo > hi) return 0;
        if (memo[lo][hi] != -1) return memo[lo][hi];
        
        long ans = 0;
        for (int i = 0; i < CHAR_CNT; i++) {
            auto itLo = idxSets[i].lower_bound(lo);
            if (itLo == idxSets[i].end() || *itLo > hi) continue;
            auto itHi = idxSets[i].upper_bound(hi); 
            itHi--; // *itLo<=hi,<=hi非空,可itHi--
            ans += (*itLo == *itHi) ? 1 : 2; // 相等时贡献c,不等时贡献c和cc
            ans += dfs(*itLo + 1, *itHi - 1, idxSets, memo); // 剥掉最外层、内层子问题
        }
        memo[lo][hi] = ans % MOD;
        return memo[lo][hi];
    }
};

扩展:不同子序列的个数,都是按字母作某种划分