leetcode 730. 统计不同回文子序列(区间dp,字符串)

题目链接

https://leetcode-cn.com/problems/count-different-palindromic-subsequences/

题意

给定一个字符串,判断这个字符串中所有的回文子序列的个数。注意回文子序列不一定连续,可以删除某些字符得到。重复的回文字符串只计算一次。

思路:

动态规划,难点:

  1. 回文字符串怎么判重
  2. 动态规划的转移方程怎么推导

在这里我们假设dp[i][j]表示从[i,..,j]字符串中含有的不重复回文字符串的总数,那么首先来看边界条件:
如果每个字符串只有一个字符,显然dp[i][i]=1
如果含有多个字符,这时候我们根据这个字符串的首尾字符s[i],s[j]是否相同分类讨论:

  1. 如果s[i]!=s[j],那么dp[i][j]表示的总个数,显然可以由dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]转化而来,两者减去中间重复的值
  2. 如果s[i]==s[j],这时候就可能出现重复的情况:
    利用双指针的思想,设low=i+1,high=j-1
    while(low<=high && s[low]!=s[j]) low++;
    while(low<=high && s[high]!=s[j]) high--;
    找到左右两边分别第一个和s[i]相同的字符
  • 如果low>high,比如aba, 这时候dp[i][j]<--dp[i+1][j-1]*2+2 //*2的原因,可以看成dp[i+1][j-1],两端加或者不加s[i]的结果;+2的原因,可看成s[i],s[i]s[i]多于两种情况的结果
  • 如果low==high, 比如aaa,以此类推,dp[i][j]<--dp[i+1][j-1]*2+1; //有重复
  • 如果low<high,比如aabaa,以此类推, dp[i][i]<--dp[i+1][j-1]*2-dp[low+1][high-1];//中间b这一部分算了两遍
class Solution {
public:
//朴素解法: O(N^2*N)==>假算法,朴素解法应该是O(2^N)
//O(N^2)预处理所有情况,然后O(N^2)判断,用unordered_set统计 =>时间复杂度O(N^2)==>假算法,因为回文子序列并不一定连续。。。

//正解:区间dp, 状态转移方程真不好想
//难点:
//1. 如何去重
//如果推出dp[i][j]的转移方程,dp[i][j] 从[i,..,j]中不同回文字符串的个数,答案dp[0][n-1]
//三种情况,对应三种不同的例子:aaa, aba, aacaa

const int Mod=1e9+7;
int countPalindromicSubsequences(string S){
    int n=S.size();
    vector<vector<int>> dp(n+1,vector<int>(n+1,0));
    //单独一个字符
    for(int i=0;i<n;i++) dp[i][i]=1;
    for(int len=1;len<n;len++){
        for(int i=0;i<n;i++){
            int j=i+len;
            if(j>=n) break;

            if(S[i]==S[j]){//最复杂的情况,需要判重
                int low=i+1;
                int high=j-1;

                while(low<=high && S[low] !=S[j]) low++;
                while(low<=high && S[high] !=S[j]) high--;
                //根据low和high的最终位置分类讨论
                if(low>high) {//例子:aba
                    dp[i][j]=dp[i+1][j-1]*2+2;//两端相等 {b,aba,a,aa}
                }
                else if(low==high){//例子:aaa {a,aaa,aa}
                    dp[i][j]=dp[i+1][j-1]*2+1;
                }
                else{//例子:aabaa 有重复{b}这个位置重复了一次
                //aba: {b,aba,a,aa}
                //aabaa: {b,aba,a,aa; aba ,aabaa,aaa,aaaa, a,aa}
                    dp[i][j]=dp[i+1][j-1]*2-dp[low+1][high-1];
                }
            }
            else{
                //上一个状态[i,j]=[i,j-1]+[i+1,j]-[i+1,j-1]
                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 dp[0][n-1];
}

// const int Mod=1e9+7;
//     int countPalindromicSubsequences(string S) {
//         int n=S.size();
//         vector<vector<int>> dp(n,vector<int>(n,0));
//         //处理长度为2时候对应的情况
//         for(int i=0;i<n-1;i++){
//             dp[i][i]=1;
//             if(S[i]==S[i+1]) dp[i][i+1]=1;
//         }
//         dp[n-1][n-1]=1;
//         //长度3以上的情况
//         for(int len=3;len<n;len++){
//             for(int i=0;i+len-1<n;i++){
//                 int j=i+len-1;
//                 if(S[i]==S[j] && dp[i+1][j-1]) dp[i][j]=1;
//             }
//         }
//         //统计
//         unordered_set<string> st;
//         int ans=0;
//         for(int i=0;i<n;i++){
//             for(int len=1;len<n;len++){
//                 int j=i+len-1;
//                 if(j>=n) break;
//                 string tmp=S.substr(i,len);
//                 if(!st.count(tmp) && dp[i][j]){
//                     cout<<tmp<<"\n";
//                     ans=(ans+1)%Mod;
//                     st.insert(tmp);
//                 }
//             }
//         }

//         return ans;
//     }
};

参考思路:
https://leetcode.com/problems/count-different-palindromic-subsequences/discuss/109507/Java-96ms-DP-Solution-with-Detailed-Explanation

posted @ 2020-12-02 11:32  xzhws  阅读(380)  评论(0编辑  收藏  举报