【LeetCode】5. 最长回文子串

题目链接

文章目录

    • Python3
      • 方法: 暴力求解 ⟮ O ( n 3 ) 、 O ( 1 ) ⟯ \lgroup O(n^3)、O(1)\rgroup O(n3)O(1)⟯
      • 方法一: 动态规划 (回文串同时去掉头尾后 依然是回文串) ⟮ O ( n 2 ) ⟯ \lgroup O(n^2)\rgroup O(n2)⟯
      • ⭐ 方法二:中心扩展法 ⟮ O ( n 2 ) 、 O ( 1 ) ⟯ \lgroup O(n^2)、O(1)\rgroup O(n2)O(1)⟯
      • ⭐ 方法三:Manacher(马拉车) 法 ⟮ O ( n ) ⟯ \lgroup O(n)\rgroup O(n)⟯ 【空间换时间】
          • 写法一: 维护盒子左右两边
          • 写法二: 维护盒子 右侧 和 中心
    • C++
      • 方法二:中心扩展法 ⟮ O ( n 2 ) 、 O ( 1 ) ⟯ \lgroup O(n^2)、O(1)\rgroup O(n2)O(1)⟯
      • 方法三:Manacher(马拉车) 法 ⟮ O ( n ) ⟯ \lgroup O(n)\rgroup O(n)⟯ 【空间换时间】
        • 写法一: 维护 盒子左右两侧下标
        • 写法二: 维护盒子 中心下标 和 右侧下标
        • Manacher(马拉车) 法 理解 参考

【LeetCode】5. 最长回文子串_第1张图片
【LeetCode】5. 最长回文子串_第2张图片

Python3

方法: 暴力求解 ⟮ O ( n 3 ) 、 O ( 1 ) ⟯ \lgroup O(n^3)、O(1)\rgroup O(n3)O(1)⟯

class Solution:
    def longestPalindrome(self, s: str) -> str:
        if s == s[::-1]:
            return s
        n = len(s)
        res = s[0]
        max_length = 1 # 至少一个字符
        for i in range(n-1): # 
            for j in range(i+1, n):
                if j - i + 1 > max_length and s[i:j+1] == s[i:j+1][::-1]: # s[i:i+1] 必然回文,直接跳过
                    res = s[i:j+1]
                    max_length = j - i + 1
                        

        return res

方法一: 动态规划 (回文串同时去掉头尾后 依然是回文串) ⟮ O ( n 2 ) ⟯ \lgroup O(n^2)\rgroup O(n2)⟯

【LeetCode】5. 最长回文子串_第3张图片

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if s == s[::-1] or n <= 1:
            return s
        
        dp = [[False]*n for _ in range(n)]
        for i in range(n):
            dp[i][i] = True
        
        max_length = 1
        index_left = 0  # 维护 前后界下标 占用的空间 似乎 比 直接维护 字符串少些

        for right in range(1, n):
            for left in range(right):
                if s[left] == s[right]:
                    if right - left <= 2: ##  aba(right-left==2)  aa(right-left==1)
                        dp[left][right] = True 
                    else:  # 长度 大于 3或2, 取决于 同时去掉两端的字符串 为回文
                        dp[left][right] = dp[left+1][right-1]
                
                ## 处理好后,判断
                if right - left + 1 > max_length and dp[left][right]: # 当长度 够长 才 决定是否更新
                    max_length = right - left + 1  # 注意长度计算
                    index_left = left 
                
        return s[index_left : index_left + max_length] # 注意 转换

【LeetCode】5. 最长回文子串_第4张图片

⭐ 方法二:中心扩展法 ⟮ O ( n 2 ) 、 O ( 1 ) ⟯ \lgroup O(n^2)、O(1)\rgroup O(n2)O(1)⟯

class Solution:
    def longestPalindrome(self, s: str) -> str:
        # 子模块 中心扩散
        def expandAroundCenter(s, left, right):
            while left >= 0 and right <= len(s)-1 and s[left] == s[right]:
                left -= 1
                right += 1

            return left+1, right-1  ## 返回 回文字符串 左右 下标    ab 01  


        # 主模块
        ## 处理特殊情况
        n = len(s)
        if s == s[::-1] or n <= 1:
            return s 
        
        max_length = 1
        index_left = 0
        for i in range(n):
            left1, right1 = expandAroundCenter(s, i, i)  ## 中心为一个字符
            left2, right2 = expandAroundCenter(s, i, i+1)  # 中心为 两个一样的字符
            if right1- left1 + 1 > max_length: 
                max_length = right1 - left1 + 1 
                index_left = left1 
            if right2 - left2 + 1 >  max_length:
                max_length = right2 - left2 + 1
                index_left  = left2  

        return s[index_left : index_left+max_length]


【LeetCode】5. 最长回文子串_第5张图片

⭐ 方法三:Manacher(马拉车) 法 ⟮ O ( n ) ⟯ \lgroup O(n)\rgroup O(n)⟯ 【空间换时间】

在这里插入图片描述
【LeetCode】5. 最长回文子串_第6张图片

写法一: 维护盒子左右两边
class Solution:
    #  子模块   中心扩散模块    盒外 以及 边界 仍需要扩展  
    def expandAroundCenter(self, s, left, right):
        while left >= 0 and right <= len(s)-1 and s[left] == s[right]:
            left -= 1
            right += 1

        ## 由于 边界是必定满足的,所以循环跳出 必定是 s[left] != s[right]
        # return s[left+1: right]  ### 注意 切片[] 是左合右开的
        return (right - left - 2) // 2  ## 注意这里要返回 回文半径 =[right - 1 - (left+1)] //2    
    # 主模块
    def longestPalindrome(self, s: str) -> str:
        """马拉车(统一处理奇偶回文串、进一步利用之前记录的对称关系)"""

        s = '#' + '#'.join(list(s)) + '#'  ## 插入特殊字符,s改造后 总长度为奇
        d = []   ## 记录 回文半径

        start, end = 0, -1   ## 用于 记录 最长回文串的起始和终止位置
        right = -1   ## 维护 盒子的 右端位置, 根据  回文串的特性, left = 2 * center - right
        left = 0   ## 维护盒子  左边 位置
        
        ## 写入 回文半径 d
        d.append(1)
        for i in range(1, len(s)):
            if i <= right: ## 位于 盒内 和边界
                mirror_left = left + right - i  ## 左边镜像的位置
                min_d = min(d[mirror_left], right-i)   ## 只扩展 盒子外的
                cur_d = self.expandAroundCenter(s, i - min_d, i + min_d)  ## 左右扩展
            else:
                cur_d = self.expandAroundCenter(s, i, i) ## 位于 盒外,无法利用之前的信息,直接左右扩展
            d.append(cur_d)

            if i + cur_d > right: ### 更新盒子 的左边界 和 右边界
                left =  i - cur_d 
                right = i + cur_d

            ## 更新  最长回文串的  位置
            if right - left > end - start:
                start = left
                end = right

        return s[start + 1 : end + 1 : 2]
写法二: 维护盒子 右侧 和 中心
class Solution:
    # 子模块
    def expandAroundCenter(self, s, left, right):
        while left >= 0 and right <= len(s)-1 and s[left] == s[right]:
            left -= 1
            right += 1

        return (right - left - 2) // 2  # 返回 回文半径

    # 主模块
    def longestPalindrome(self, s: str) -> str:
        # 改造 s ,统一成  奇数长度
        s = '#' + '#'.join(list(s)) + '#'

        d = []  # 记录回文半径

        start, end = 0, -1  ## 回文串 起始 和 结束 下标
        center = 0  # 盒子中心坐标
        right = -1 # 盒子 右侧下标

        d.append(1)
        for i in range(1, len(s)):
            if i <= right: 
                mirror_left = 2 * center - i
                min_d = min(d[mirror_left], right-i)
                cur_d = self.expandAroundCenter(s, i - min_d, i + min_d)
            else:
                cur_d = self.expandAroundCenter(s, i, i)
            d.append(cur_d)
            
            # 更新 盒子
            if i + cur_d > right:
                center = i 
                right = i + cur_d 

            # 更新 最长回文串 位置
            if 2 * cur_d + 1 > end - start:
                start = i - cur_d 
                end = i + cur_d 
        return s[start + 1 : end + 1 : 2]

【LeetCode】5. 最长回文子串_第7张图片

C++

方法二:中心扩展法 ⟮ O ( n 2 ) 、 O ( 1 ) ⟯ \lgroup O(n^2)、O(1)\rgroup O(n2)O(1)⟯

class Solution {
public:
    // 子模块
    pair<int, int> expandAroundCenter(const string & s,int left, int right){
        while (left >= 0 && right <= s.size()-1 && s[left] == s[right]){
            --left;
            ++right;
        }
        return {left + 1, right - 1};
    }

    // 主模块
    string longestPalindrome(string s) {
        int start = 0, end = 0; // 当前 回文串 的起止下标
        for (int i = 0; i < s.size(); ++i){
            auto [left1, right1] = expandAroundCenter(s, i, i);
            auto [left2, right2] = expandAroundCenter(s, i, i+1);
            if (right1 - left1 > end - start){
                start = left1;
                end = right1;
            }
            if (right2 - left2 > end - start){
                start = left2;
                end = right2;
            }
        }
        return s.substr(start, end - start + 1);   // 注意写法
    }
};

方法三:Manacher(马拉车) 法 ⟮ O ( n ) ⟯ \lgroup O(n)\rgroup O(n)⟯ 【空间换时间】

写法一: 维护 盒子左右两侧下标
class Solution {
public:
    // 子模块
    int expandAroundCenter(const string &s, int left, int right){
        while (left >= 0 && right <= s.size()-1 && s[left] == s[right]){
            --left;
            ++right;
        }
        return (right - left - 2)/2;  // 返回  回文半径
    }

    // 主模块
    string longestPalindrome(string s){
        // 修改s  插入特殊字符
        string t = "#";  // 注意是字符串 形式
        for (char c : s){
            t += c;
            t += '#';
        }
        t += '#';
        s = t;

        int start = 0, end = -1;  // 维护 最长回文串的 起止下标
        int left = 0, right = -1;
        vector<int> d; 
        d.emplace_back(1);
        for (int i = 1; i < s.size(); ++i){
            int cur_d;
            if (i <= right){// 位于盒内 或 边界
                int mirror_left = left + right - i;
                int min_d = min(d[mirror_left], right - i);
                cur_d = expandAroundCenter(s, i - min_d, i + min_d);
            }
            else{
                cur_d = expandAroundCenter(s, i, i);  //奇数情况
            }
            d.emplace_back(cur_d);

            if (i + cur_d > right){
                left = i - cur_d;
                right = i + cur_d;
            }
            
            if (right - left > end - start){
                start = left;
                end = right;
            }
        }
        string res;
        for (int i = start; i <= end; ++i){
            if (s[i] != '#'){
              res += s[i];  
            }
        }
        return res;
    }
};
写法二: 维护盒子 中心下标 和 右侧下标
class Solution {
public:
    // 子模块
    int expandAroundCenter(const string &s, int left, int right){
        while (left >= 0 and right <= s.size() - 1 && s[left] == s[right]){
            --left;
            ++right;
        }
        return (right - left - 2)/2; // 返回 回文半径
    }

    // 主模块
    string longestPalindrome(string s) {
        // 修改 s   统一成 奇长度
        string t = "#";
        for (char c : s){
            t += c;
            t += '#';
        }
        t += '#';
        s = t;

        // 存储 回文半径
        vector<int> d;
        d.emplace_back(1);
        int start = 0, end = -1; // 最长 回文串 的起止下标
        int center = 0, right = -1;  // 盒子 的 中间下标 和 右侧下标
        for (int i = 1; i < s.size(); ++i){
            int cur_d;
            if (i <= right){
                int mirror_left = 2 * center - i;
                int min_d = min(d[mirror_left], right-i);
                cur_d = expandAroundCenter(s, i - min_d, i + min_d);
            }
            else{
                cur_d = expandAroundCenter(s, i, i);   // 盒子外, 中心扩展
            }
            d.emplace_back(cur_d);

            if (i + cur_d > right){
                center = i;
                right = i + cur_d;
            }

            // 更新 最长 回文串 的起止下标
            if (2 * cur_d + 1 > end - start){
                start = i - cur_d;
                end = i + cur_d;
            }
        }
        string res;
        for (int i = start; i <= end; ++i){ // 注意 起止
            if (s[i] != '#'){
                res += s[i];
            }
        }
        return res;
    }
};
Manacher(马拉车) 法 理解 参考

参考链接:https://www.bilibili.com/video/BV173411V7Ai/?spm_id_from=333.337.search-card.all.click&vd_source=f722c145eae91a5b6df588c0ca0f6dbb

你可能感兴趣的:(#,编程练习,合集,LeetCode,Python3,C++)