zoukankan      html  css  js  c++  java
  • LeetCode 730. Count Different Palindromic Subsequences (区间DP)

    题意

    给一个字符串S,求它所有子序列不同非空回文串的数量。字符串由 'a' 'b' 'c' 'd' 四个字母组成。

    由于题目要求的是不同回文串。 abba 的回文串子序列为 a,b,aba,abba 其中 aba 只能算一次。

    最近做 区间DP 的题,习惯起手写

    for (int l = 0; l < n; l++) {
        for (int s = 0; s + l < n; s++) {
            int e = s + l;
            .....
        }
    }    

    外层枚举区间长度,内层枚举区间起始位置。

    设 dp[i][j] 表示  S[i...j] 子序列中包含的回文串的数量。

    解法1、枚举回文串第一个字母

    设回文串第一个字母是 a 则最后一个字母也要是 a 然后找到在区间 [s,t] 中第一个和最后一个 a 的位置 x 和 y ,则,首字母为 a 的回文串个数为 dp[x-1][y+1] 

    因为,如果使用的不是两边的 a 而是内部的 a 作为边界,能发现内部 a 做边界组成的回文串 两边的 a 做边界时都组成。

    最后再用两边的 a 组成 aa 以及 单个 a。当范围内只存在一个 a 时,则能贡献的子串只有 1 个。

    很难受的是直接递推超时了,递归才勉强能过。。。

    class Solution {
    public:
        int countPalindromicSubsequences(string S) {
            // dp[i][j] 表示 s[i...j] 包含的子序列个数
            int n = S.size();
            vector<vector<int>> pos(4, vector<int>());
            vector<vector<int>> dp(n, vector<int>(n, 0));
            for (int i = 0; i < n; i++) {
                pos[S[i] - 'a'].push_back(i);
            }
            return dfs(S, pos, dp, 0, n - 1);
        }
        int dfs(string &S, vector<vector<int>> &pos, vector<vector<int>> &dp, int s, int t) {
            if (s + 1 >= t) return t - s + 1;
            if (dp[s][t]) return dp[s][t];
            int ans = 0;
            for (int i = 0; i < 4; i++) {
                if (pos[i].empty()) continue;
                auto first_pos = lower_bound(pos[i].begin(), pos[i].end(), s);
                auto end_pos = upper_bound(pos[i].begin(), pos[i].end(), t) - 1;
                if (first_pos == pos[i].end() || *first_pos > t) continue;
                int f = *first_pos, e = *end_pos;
                ans = (ans + 1) % 1000000007;
                if (f != e) ans = (ans + 1) % 1000000007;
                if (f + 1 < e) {
                    ans = (ans + dfs(S, pos, dp, f + 1, e - 1)) % 1000000007;
                }
            }
            return dp[s][t] = ans;
        }
    };

    解法2 (https://leetcode.com/problems/count-different-palindromic-subsequences/discuss/109507/Java-96ms-DP-Solution-with-Detailed-Explanation

    求 S[s..e] 的回文串数

    当 S[s] != S[e] 时,可得公式

    dp[s][e] = dp[s+1][e] + dp[s][e-1] - dp[s+1][e-1];

    即 (包含s + 两边都不含的) 和 (包含e+ 两边都不含的)- 两边都不含的

    当 S[s] == S[e] 时 还用上面的公式会出现重复。

    比如 aaa ,计算 dp[0][2] 时 ,dp[0][1] 包含 (aa) 和 dp[1][2] 包含的 aa 重复但是没有被减去。

    而如果想要减去重复部分,就要找到和边界位置相同的字母。假设边界字母是 a,

    当没有和边界位置相同的字母, dp[s][e] = dp[s+1][e-1](两边都不含的)+ dp[s+1][e-1](两边都不含的 + 两边的字母)+ 1 (两边的字母组成 aa) + 1 (单个首字母 a)

    当存在和边界位置相同的字母,且只有一个,dp[s][e] = dp[s+1][e-1](两边都不含的)+ dp[s+1][e-1](两边都不含的 + 两边的字母)+ 1 (两边的字母组成 aa)

    当存在和边界位置相同的字母,且有多个(2个及以上) 中间的组成的,可能会和中间的a加上边界之后重复,比如 a(aaa)a 中间组成a aa aaa 加上两边的a 就变成了 aaa aaaa aaaaa 那么 aaa就重复计算了,找到除了边界的两个a,最外层的两个a,设位置是 low 和 high 那么,dp[low+1][high-1] 中所有子序列和low high 组成的回文串与和s e 组成的回文串都会重复。所以要减去dp[low+1][high-1]。dp[s][e] = dp[s+1][e-1] * 2 - dp[low + 1][high - 1]; 

    其实我也不是很懂。。。。瞎写的。。。真的好难啊。。。。 

    class Solution {
    public:
        int countPalindromicSubsequences(string S) {
            int n = S.size();
            int dp[n][n];
            int mod = 1e9 + 7;
            for (int l = 0; l < n; l++) {
                for (int s = 0; s + l < n; s++) {
                    int e = s + l;
                    if (l <= 1) {
                        dp[s][e] = l + 1;
                    } else if (S[s] == S[e]) {
                        int low = s + 1, high = e - 1;
                        // 寻找最近的和 S[s](S[e]) 相同的字母的位置
                        while (low <= high && S[low] != S[e]) low++;
                        while (high >= low && S[high] != S[e]) high--;
                        
                        if (low > high) dp[s][e] = dp[s+1][e-1] * 2 + 2;
                        else if (low == high) dp[s][e] = dp[s+1][e-1] * 2 + 1;
                        else dp[s][e] = dp[s+1][e-1] * 2 - (low + 1 < high ? dp[low + 1][high - 1] : 0);
                    } else {
                        dp[s][e] = dp[s+1][e] + dp[s][e-1] - dp[s+1][e-1];
                    }
                    dp[s][e] = (dp[s][e] % mod + mod) % mod;
                }
            }
            return dp[0][n - 1];
        }
    };
  • 相关阅读:
    python redis操作
    subprocess模块的使用
    tcpcopy 流量复制工具
    Python名称空间与闭包
    python 偏函数
    Python面向对象的特点
    vsftpd 安装及使用虚拟用户配置
    shell 并发脚本
    Centos7 搭建LVS DR模式 + Keepalive + NFS
    python pip 升级
  • 原文地址:https://www.cnblogs.com/wenruo/p/12704967.html
Copyright © 2011-2022 走看看