LeetCode hot 100—最长回文子串

题目

给你一个字符串 s,找到 s 中最长的 回文 子串。

示例

示例 1:

复制代码
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

复制代码
输入:s = "cbbd"
输出:"bb"

分析

动态规划法

使用动态规划来解决最长回文子串问题的核心思想是利用子问题的解来构建更大问题的解。具体来说,我们定义一个二维布尔数组 dp[i][j] ,其中 dp[i][j] 表示字符串 s 从索引 i 到索引 j 的子串是否为回文串。

状态定义

dp[i][j]:表示子串 s[i...j] 是否为回文串,true 表示是,false 表示不是。

状态转移方程

当 s[i] == s[j] 时:

  • 如果 j - i < 2(即子串长度为 1 或 2),那么 dp[i][j] = true 。例如,单个字符一定是回文串,两个相同字符组成的子串也是回文串。
  • 如果 dp[i + 1][j - 1] 为 true ,那么 dp[i][j] = true 。也就是说,当两端字符相同,且去掉两端字符后的子串也是回文串时,当前子串就是回文串。

当 s[i] != s[j] 时,dp[i][j] = false 。

初始化

对于单个字符,即 i == j 时,dp[i][j] = true ,因为单个字符一定是回文串。

遍历顺序

由于状态转移方程依赖于 dp[i + 1][j - 1] ,所以我们需要按照子串长度从小到大的顺序进行遍历,即先遍历长度为 1 的子串,再遍历长度为 2 的子串,以此类推。

记录最长回文子串

在遍历过程中,记录下最长回文子串的起始位置和长度,最后根据这些信息截取最长回文子串。

时间复杂度:O(), 是字符串的长度

空间复杂度:O()

cpp 复制代码
class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.length();
        if (n < 2) {
            return s;
        }
        // 初始化 dp 数组
        std::vector<std::vector<bool>> dp(n, std::vector<bool>(n, false));
        int start = 0;  // 最长回文子串的起始位置
        int maxLen = 1; // 最长回文子串的长度
        // 单个字符一定是回文串
        for (int i = 0; i < n; ++i) {
            dp[i][i] = true;
        }
        // 枚举子串长度
        for (int len = 2; len <= n; ++len) {
            // 枚举子串的起始位置
            for (int i = 0; i <= n - len; ++i) {
                int j = i + len - 1; // 子串的结束位置
                if (s[i] == s[j]) {
                    if (len <= 2) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                } else {
                    dp[i][j] = false;
                }
                // 更新最长回文子串的信息
                if (dp[i][j] && len > maxLen) {
                    start = i;
                    maxLen = len;
                }
            }
        }
        return s.substr(start, maxLen);
    }
};

中心扩展法

可以使用中心扩展法来找出字符串 s 中最长的回文子串。该方法的核心思想是,回文串具有对称性,因此可以以每个字符或者每两个相邻字符为中心,向两边扩展来判断是否构成回文串。

具体步骤如下:

  • 遍历字符串 s 中的每个字符,将其作为回文串的中心。
  • 对于每个中心,分别考虑两种情况:
    • 以单个字符为中心扩展,形成奇数长度的回文串。
    • 以两个相邻字符为中心扩展,形成偶数长度的回文串。
  • 在扩展过程中,不断比较左右字符是否相等,若相等则继续扩展,直到不相等为止。
  • 记录每次扩展得到的回文串的长度和起始位置,最终找出最长的回文子串。

时间复杂度:O(), 是字符串的长度

空间复杂度:O(1)

cpp 复制代码
class Solution {
public:
    string longestPalindrome(string s) {
        if (s.empty()) return "";
        int start = 0, maxLen = 0;
        // 遍历字符串中的每个字符
        for (int i = 0; i < s.length(); ++i) {
            // 以单个字符为中心扩展
            int len1 = expandAroundCenter(s, i, i);
            // 以两个相邻字符为中心扩展
            int len2 = expandAroundCenter(s, i, i + 1);
            // 取两种情况中的最大长度
            int len = max(len1, len2);
            if (len > maxLen) {
                maxLen = len;
                // 计算最长回文子串的起始位置
                start = i - (len - 1) / 2;
            }
        }
        // 截取最长回文子串
        return s.substr(start, maxLen);
    }

private:
    // 中心扩展函数
    int expandAroundCenter(const string& s, int left, int right) {
        while (left >= 0 && right < s.length() && s[left] == s[right]) {
            --left;
            ++right;
        }
        // 返回回文串的长度
        return right - left - 1;
    }
};    
相关推荐
jimy15 小时前
c++的静态类型和动态类型(二)
开发语言·c++
我命由我123455 小时前
Photoshop - Photoshop 使用魔棒工具选择单独的区域
学习·ui·职场和发展·求职招聘·职场发展·学习方法·photoshop
stolentime5 小时前
AT_arc182_a [ARC182A] Chmax Rush!题解
数据结构·c++·算法·动态规划
longlongzihan5 小时前
LeetCode 189. 轮转数组:从直观模拟到最优原地算法
c++·算法·leetcode
爱喝热水的呀哈喽6 小时前
cpp二分库函数 lambda捕获
算法
keke.shengfengpolang6 小时前
2027届统计学专业求职政策研究与产业研究:从岗位要求到材料准备的技术路径
职场和发展
longlongzihan6 小时前
LeetCode 76. 最小覆盖子串:滑动窗口算法详解
算法·leetcode·滑动窗口
写后端的胖头鱼13 小时前
一文详解B+树原理(附场景+示例)
数据结构·b树
怕浪猫15 小时前
RAG 面试 6 连问,从原理到优化全部覆盖
python·算法·面试
无线通信科研笔记15 小时前
IEEE TVT 2026 论文精读与完整复现|相位误差如何重塑近场 RIS 的幅相响应
论文阅读·人工智能·python·算法·论文笔记