leetcode--5. 最长回文子串(dp/字符串hash/manacher)

记录
23:29 2024-2-5

https://leetcode.cn/problems/longest-palindromic-substring/

1. dp方法

dp[i][j] s[i, j] 之间能否构成回文子串
[i,j]之间是否能够构成需要考虑[i+1, j-1]是否构成回文子串且s[i] == s[j]
当j-1 >= i+1 时候说明正好是 俩个相邻的字符,此时如果s[i] == s[j]就肯定可以构成回文子串

\[dp[i][j] = \begin{cases} dp[i + 1][j - 1] \qquad if \ \ j - 1 >= i + 1 \&\ \& s[i] == s[j] \\ 1 \qquad \qquad \qquad \qquad else \ \ if \ \ s[i] == s[j] \end{cases} \]

点击查看代码
class Solution {
public:

    // dp[i][j] [i, j] 之间能否构成回文子串
    /* dp[i][j] = {
        dp[i + 1][j - 1]  if j - 1 >= i + 1 && s[i] == s[j]
        1                 else s[i] == s[j]
    }*/
    string longestPalindrome(string s) {
        int dp[s.size()][s.size()];
        memset(dp, 0, sizeof(dp));
        int begin = 0, maxSize = 1; 
        for(int i = s.size() - 1; i >= 0; i--) dp[i][i] |= 1;
        for(int i = s.size() - 1; i >= 0; i--) {
            for(int j = i + 1; j < s.size(); j++) {
                if(s[i] == s[j]) {
                    if(j-1 >= i+1) {
                        dp[i][j] |= dp[i+1][j-1];
                    } else  {
                        dp[i][j] |= 1;
                    }
                    //注意需要判断dp[i][j]
                    if(dp[i][j] && maxSize < j - i + 1) {
                        maxSize = j - i + 1;
                        begin = i;
                    }
                }
            }
        }
        return s.substr(begin, maxSize);
    }
};

2.字符串hash

字符串hash
hash函数是一个可以将输入映射为值的函数(我自己理解的,更加学术的定义我清楚),这个hash函数最好可以让不同值得到不同的结果,如果有冲突的话可以用开放地址放、散列法。也可以使用多个hash进行区分(详情如数据库原理中的cuckoo hashing--布谷hash)

字符串hash就是将字符串转化为一个值,这里的方法是将字符串看成P进制数,P取值为131/13331,因为此时产生冲突的概率低(细节证明我猜需要数论知识,哈哈没学过)

然后就可以遍历字符串s中的每个位置i:

  1. 奇回文串 s[i - midp ~ i] = reverse(s[i ~ i + midp])
  2. 偶回文串 s[i - midq ~ i - 1] = reverse(s[i ~ i + midq - 1]) (使用了不同midp、midq分别用来二分)

前缀hash值很容易计算
注意对于reverse的计算,可以维护一个类似于前缀hash值的,不过开始是从末尾开始:

  1. 对于prefixHash f[r] - f[l-1] * p[r-l+1] (prefixHash就是上面 s[i - midp ~ i] = reverse(s[i ~ i + midp]) 中左边等式)
  2. 对于reverseHash f[l] - f[r+1] * p[r-l+1]
点击查看代码
class Solution {
public:

    // 字符串hash
    string longestPalindrome(string s) {
        // 为了方便处理,加一个空格
        s = ' ' + s;

        unsigned long long prefixHash[s.size() + 1], revserHash[s.size() + 1], p[s.size() + 1];
        memset(prefixHash, 0, sizeof(prefixHash));
        memset(revserHash, 0, sizeof(revserHash));
        memset(p, 0, sizeof(p));
        p[0] = 1;
        for(int i = 1; i < s.size(); i++) {
            // [1 ~ i]
            prefixHash[i] = prefixHash[i - 1] * 131 + s[i] - 'a' + 1;
            p[i] = p[i - 1] * 131;
        }
        for(int i = s.size() - 1; i >= 1; i--) {
            // [s.size() - i ~ i] 逆向计算
            revserHash[i] = revserHash[i + 1] * 131 + s[i] - 'a' + 1;
        }

        // 枚举可能的中心位置
        int begin = 1, maxSize = 1;
        for(int i = 1; i < s.size(); i++) {
            int lp = 1, rp = min(i, (int)s.size() - i);

            int lq = 1, rq = min(i, (int)s.size() - i);

            // 二分法寻找
            while(true) {
                if(lp > rp) break;

                int midp = (lp + rp) / 2;
                

                //if(2 * mid < maxSize) break;

                if(i - midp - 1 < 0 || i + midp + 1 > s.size()) {
                    rp = midp - 1;
                    continue;
                }

                // s[i - midp ~ i] = reverse(s[i ~ i + midp])
                // 对于prefixHash f[r] - f[l-1] * p[r-l+1]
                // 对于reverseHash f[l] - f[r+1] * p[r-l+1]
                if(prefixHash[i] - prefixHash[i - midp - 1] * p[midp + 1] ==
                revserHash[i] - revserHash[i + midp + 1] * p[midp + 1]) {
                    lp = midp + 1;
                    if(2 * midp + 1 > maxSize) {
                        maxSize = 2 * midp + 1;
                        begin = i - midp;
                    }
                } else {
                    rp = midp - 1;
                }
            }

            while(true) {
                if(lq > rq) break;

                int midq = (lq + rq) / 2;

                if(i - midq - 1 < 0 || i + midq > s.size()) {
                    rq = midq - 1;
                    continue;
                }
                // s[i - midq ~ i - 1] = reverse(s[i ~ i + midq - 1])
                if(prefixHash[i - 1] - prefixHash[i - midq - 1] * p[midq] ==
                revserHash[i] - revserHash[i + midq] * p[midq]) {
                    lq = midq + 1;
                    if(2 * midq > maxSize) {
                        maxSize = 2 * midq;
                        begin = i - midq;
                    }
                } else {
                    rq = midq - 1;
                }
            }
        }        
        return s.substr(begin, maxSize);
    }
};

3.manacher

点击查看代码
class Solution {
public:
    // manacher
    string longestPalindrome(string s) {
        int n = s.size();
        vector<int> d1(n);
        for (int i = 0, l = 0, r = -1; i < n; i++) {
            int k = (i > r) ? 1 : min(d1[l + r - i], r - i + 1);
            while (0 <= i - k && i + k < n && s[i - k] == s[i + k]) {
                k++;
            }
            d1[i] = k--;
            if (i + k > r) {
                l = i - k;
                r = i + k;
            }
        }

        vector<int> d2(n);
        for (int i = 0, l = 0, r = -1; i < n; i++) {
            int k = (i > r) ? 0 : min(d2[l + r - i + 1], r - i + 1);
            while (0 <= i - k - 1 && i + k < n && s[i - k - 1] == s[i + k]) {
                k++;
            }
            d2[i] = k--;
            if (i + k > r) {
                l = i - k - 1;
                r = i + k;
            }
        }
        int begin = 0, maxSize = 1;
        for(int i = 0; i < n; i++) {
            if(2 * d1[i] - 1 > maxSize) {
                maxSize = 2 * d1[i] - 1;
                begin = i - d1[i] + 1;
            }
            if(2 * d2[i] > maxSize) {
                maxSize = 2 * d2[i];
                begin = i - d2[i];
            }
        }  
        return s.substr(begin, maxSize);      
    }
};
posted @ 2024-02-05 23:36  57one  阅读(1)  评论(0编辑  收藏  举报