LeetCode - 解题笔记 - 5 - Longest Palindromic Substring

Longest Palindromic Substring

Solution 1

双向维护状态的动态规划。 a n s [ i ] [ j ] ans[i][j] ans[i][j]表示从 i i i j j j的子串是否为回文串,状态维护如下:
{ a n s [ i ] [ j ] : = t r u e i = j a n s [ i ] [ j ] : = t r u e j − i = 1 , s [ i ] = s [ j ] a n s [ i ] [ j ] : = a n s [ i − 1 ] [ j + 1 ] j − i > 1 , s [ i ] = s [ j ] , a n s [ i − 1 ] [ j − 1 ] = t r u e \begin{cases} ans[i][j] := true & i = j \\ ans[i][j] := true & j - i = 1 ,\quad s[i] = s[j] \\ ans[i][j] := ans[i - 1][j + 1] & j - i > 1,\quad s[i] = s[j] ,\quad ans[i - 1][j - 1] = true \end{cases} ans[i][j]:=trueans[i][j]:=trueans[i][j]:=ans[i1][j+1]i=jji=1,s[i]=s[j]ji>1,s[i]=s[j],ans[i1][j1]=true
在维护构成中需要注意一点,两重循环的顺序要保证当前考察子串的内部状态已知,因此需要先确定尾边界,再迭代头边界,以保证内部子串的状态已经计算。

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) n n n为字符串的长度
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2) n n n为字符串的长度
class Solution {
public:
    string longestPalindrome(string s) {
        vector<vector<bool>> ans(s.size());
        
        // 初始化情形:每个字符本身是一种回文,最终的答案初始化为原始字串的第一个字符
        for (int i = 0; i < s.size(); ++i) {
            ans[i] = vector<bool>(s.size());
            ans[i][i] = true;
        }
        
        int lenMax = 1;
        string ansStr = s.substr(0, 1);
        
        // 这里的一个处理:先迭代尾边界,再迭代头边界,这样能保证我们考察更长子串时,更短的子串已经被确认过(考察0到3时,1到2要确保考察到)
        for (int j = 0; j < s.size(); ++j) {
            for (int i = 0; i < j; ++i) {
                if (j - i == 1) {
                    ans[i][j] = (s[i] == s[j]);
                }
                else {
                    ans[i][j] = (s[i] == s[j]) && ans[i + 1][j - 1];
                }
                if (ans[i][j] && j - i + 1 > lenMax) {
                    lenMax = j - i + 1;
                    ansStr = s.substr(i, j - i + 1);
                }
                // cout << i << " " << j << " " << ans[i][j] << " " << lenMax << " " << ansStr << endl;
            }
        }
        
        return ansStr;
    }
};

Solution 2

一个非常有趣的思路:如果一个子串是回文,将整个字符串翻转,回文串本身将会构成“公共子串”,我们的目标就是寻找“最长公共子串”。但是有一种可能是,字符串的头尾存在镜像对称的部分,这样的部分会在翻转过后被找到座位公共子串,这个时候就要确定一下两个字符串中的公共子串部分的索引是否对应同一个部分(注意,翻转字符串中公共子串的头索引对应原字符串中公共子串的尾索引)。

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) n n n为字符串的长度
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2) n n n为字符串的长度
class Solution {
public:
    string longestPalindrome(string s) {
        string sRev(s);
        reverse(sRev.begin(), sRev.end());
        cout << sRev << endl;
        
        vector<vector<int>> ans(s.size());
        for (int i = 0; i < s.size(); ++i) {
            ans[i] = vector<int>(sRev.size(), 0);
        }
        
        for (int j = 0; j < sRev.size(); ++j) {
            if (s[0] == sRev[j]) {
                ans[0][j] = 1;
            }
        }
        
        for (int i = 0; i < sRev.size(); ++i) {
            if (s[i] == sRev[0]) {
                ans[i][0] = 1;
            }
        }
        
        
        int lenMax = 1;
        string ansStr = s.substr(0, 1);
        
        for (int i = 1; i < s.size(); ++i) {
            for (int j = 1; j < sRev.size(); ++j) {
                // cout << i << " " << j << " ";
                if (s[i] == sRev[j]) {
                    ans[i][j] = ans[i - 1][j - 1] + 1;
                }
                else {
                    ans[i][j] = 0;
                }
                // 这种逻辑下,有可能出现首位相同的情形,需要检查索引是否一致
                if (ans[i][j] > lenMax && (i == sRev.size() - (j - ans[i][j] + 1) - 1 )) {
                    lenMax = ans[i][j];
                    ansStr = s.substr(i - lenMax + 1, lenMax);
                }
                // cout << ans[i][j] << " " << lenMax << " " << ansStr << endl;
            }
        }
        
        return ansStr;
    }
};

Solution 3

(来自官方)中心扩展法,其实是非常暴力的思路:以一个字符为中心,向两侧扩展检查回文性。对于偶数长度的回文子串,这个中心在两个字符的中心。官方题解中的单偶数中心处理比较巧妙,因此实现了一下。

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) n n n为字符串的长度
  • 空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
public:
    string longestPalindrome(string s) {
        int lenMax = 1;
        string ansStr = s.substr(0, 1);
        for (int i = 0; i < s.size(); ++i) {
            int lenOdd = expandStringChecker(s, i, i);
            int lenEven = expandStringChecker(s, i, i + 1); // 中心在两个字符中间
            int len = max(lenOdd, lenEven);
            if (len > lenMax){
                lenMax = len;
                ansStr = s.substr(i - (len - 1) / 2, len);
            }
        }
        return ansStr;
    }
private:
    int expandStringChecker(string s, int left, int right) {
        int iLeft = left, iRight = right;
        while (iLeft >= 0 && iRight < s.size() && s[iLeft] == s[iRight]) {
            iLeft --;
            iRight ++;
        }
        
        return iRight - iLeft - 1;
    }
};

Solution 4

Solution 1的Python实现。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        ans = list()
        for i in range(len(s)):
            ans.append([False] * len(s))
            ans[i][i] = True
            
        len_max = 1
        ans_str = s[0: 1]
        
        for j in range(len(s)):
            for i in range(j):
                if j - i == 1:
                    ans[i][j] = (s[i] == s[j])
                else:
                    ans[i][j] = (s[i] == s[j]) and ans[i + 1][j - 1]
                    
                if ans[i][j] and (j - i + 1 > len_max):
                    len_max = j - i + 1
                    ans_str = s[i: j+1]
                    
                # print(i, j, ans[i][j], len_max, ans_str)
        
        return ans_str
            
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值