动态规划——最长回文子串

这篇具有很好参考价值的文章主要介绍了动态规划——最长回文子串。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

题目描述

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

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

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

示例 2:

 输入:s = "cbbd"
 输出:"bb"

解题方法

1、动态规划算法

解题思路

(1)考虑回文串的性质:若一个子串是回文串并且长度大于2,则将其首尾两个字母去除后,该子串仍然是一个回文串,反过来思考,若一个子串是回文串,在其首尾分别添加一个字母,若字母相同,则形成的子串也是回文串。

(2)状态表示:回文串有三个重要的性质:起始字母下标终止字母下标以及长度,其中长度可以由前两个性质推导得出,则使用二维数组dp(i,j)表示,dp(i,j)的值表示的也不再是区间[i,j]的最大回文子串的长度了,而是表示区间 [i,j]是否是回文串

若 dp(i,j)=true,表示Si....Sj是一个回文串,反之则不是一个回文串。

(3)初始化:每个字符本身就是一个回文串

for (int i = 0; i < n; i++) 
 {
     dp[i][i] = true;
 }

(4)状态转移:由(1)可得,当j-i>=3时,dp(i,j)的值由两个因素决定:s[i]与s[j]的值dp(i+1,j-1),并且仅当以下两个条件同时成立时,dp(i,j)为true

最大回文子串 动态规划,动态规划,动态规划,算法

当j-i<3时,存在两种情况:

①j-i=1,则dp(i,j)一定为true(a、b);

②j-i=2,若此时s[i]==s[j],则dp(i,j)为true(ab、aa);

其中第二种情况可以归纳到一般情况的计算中,只需要添加一个条件判断即可。

因为需要使用到dp(i+1,j-1)的值,递推时从长度较小的字符串向长度较长的字符串进行转移(即先计算较小的区间,再计算较大的区间)。

(5)代码流程:当区间大小为1时(即i==j),一定是回文串,因此只需要讨论区间长度在[2,n]之间的情况。外层循环表示每次判断的区间大小L内层循环固定右边界(即i的大小),j的值可以由L和i推导得到(j=L+i-1),因此不需要再加一层循环。同时需要注意左边界越界问题(j>=n),一旦越界就可以退出当前循环,右边界不存在越界问题。

在循环中,一旦s[i]!=s[j]则表示区间[i,j]不可能形成回文串,置dp(i,j)=false;若s[i]==s[j],当L<=3时不需要判断dp(i+1,j-1)的情况,直接置dp(i,j)=true,当L>3时,需要判断dp(i+1,j-1),当且仅当dp(i+1,j-1)=true时,置dp(i,j)= true

代码实现
class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        // 特殊情况,直接返回字符串即可
        if (n < 2) {
            return s;
        }

        int maxLen = 1;
        int begin = 0;
        // dp[i][j] 表示 s[i..j] 是否是回文串
        vector<vector<int>> dp(n, vector<int>(n));
        // 初始化:所有长度为 1 的子串都是回文串
        for (int i = 0; i < n; i++) {
            dp[i][i] = true;
        }
        // 递推开始
        // 先枚举子串长度
        for (int L = 2; L <= n; L++) {
            // 枚举左边界,左边界的上限设置可以宽松一些
            for (int i = 0; i < n; i++) {
                // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                int j = L + i - 1;
                // 如果右边界越界,就可以退出当前循环
                if (j >= n) {
                    break;
                }

                if (s[i] != s[j]) {
                    dp[i][j] = false;
                } else {
                    // 处理当j-i==2时的特殊情况
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }

                // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文
                // 此时记录回文长度和起始位置
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    begin = i;
                }
            }
        }
        return s.substr(begin, maxLen);
    }
};
复杂度分析

(1)时间复杂度

外层循环n次,内层循环n次,则时间复杂度为O(n^2)

(2)空间复杂度

动态规划数组存储空间为nxn,则空间复杂度为O(n^2)

2、中心扩展算法

解题思路

(1)在动态规划方法中,每一次判断s[i]==s[j]之后,我们都会再判断dp(i+1,j-1)的情况。事实上可以考虑以回文子串为扩展中心,向(i-1,j+1)方向扩展,若s[i-1]==s[j+1]则可以扩展回文子串的长度,一旦不相等停止继续扩展回文子串。

(2)代码流程:先枚举所有回文中心,再尝试向左向右同时进行扩展,最后求出所有扩展回文串的最大值得到最大回文串长度。

注意:因为奇数长度的回文串和偶数长度的回文串结构不太一样,偶数长度的回文串没有中心字符,因此枚举时需要以长度为1的子串作为回文中心(扩展得到奇数长度的回文串)也需要以长度为2的子串作为回文中心(扩展得到偶数长度的回文串)。

(3)标准模板库——pair

作用:

①pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。

pair<string, string> anon;

②另一个应用是,当一个函数需要**返回2个数据**的时候,可以选择pair。

pair<int, int> expandAroundCenter(const string& s, int left, int right)
 {
     // 省略
     return {left + 1, right - 1};
 }

性质:

①pair中的两个数据可以具有不同的数据类型

pair<int, double> p1(1, 1.2);

②两个数据可以分别用pair的两个公有函数first和second访问

pair<int ,double> p1;
p1.first = 1; 
p1.second = 2.5;
cout<<p1.first<<' '<<p1.second<<endl;
代码实现
class Solution {
public:
    pair<int, int> expandAroundCenter(const string& s, int left, int right) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
            --left;
            ++right;
        }
        // 返回满足要求的回文串起始索引
        return {left + 1, right - 1};
    }

    string longestPalindrome(string s) {
        // 使用变量start和end跟踪最大扩展回文子串
        int start = 0, end = 0;
        // 枚举所有可能的长度为1或2的回文中心
        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);
    }
};
复杂度分析

(1)时间复杂度

回文中心共有n个,每个回文中心最多向外扩展n次,则时间复杂度为O(n^2)

(2)空间复杂度

使用了变量start、end作为跟踪变量(常数),则空间复杂度为O(n)

3、Manacher算法

class Solution {
public:
    int expand(const string& s, int left, int right) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
            --left;
            ++right;
        }
        return (right - left - 2) / 2;
    }

    string longestPalindrome(string s) {
        int start = 0, end = -1;
        string t = "#";
        for (char c: s) {
            t += c;
            t += '#';
        }
        t += '#';
        s = t;

        vector<int> arm_len;
        int right = -1, j = -1;
        for (int i = 0; i < s.size(); ++i) {
            int cur_arm_len;
            if (right >= i) {
                int i_sym = j * 2 - i;
                int min_arm_len = min(arm_len[i_sym], right - i);
                cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
            } else {
                cur_arm_len = expand(s, i, i);
            }
            arm_len.push_back(cur_arm_len);
            if (i + cur_arm_len > right) {
                j = i;
                right = i + cur_arm_len;
            }
            if (cur_arm_len * 2 + 1 > end - start) {
                start = i - cur_arm_len;
                end = i + cur_arm_len;
            }
        }

        string ans;
        for (int i = start; i <= end; ++i) {
            if (s[i] != '#') {
                ans += s[i];
            }
        }
        return ans;
    }
};

(1)时间复杂度:O(n)

(2)空间复杂度:O(n)

该算法时间复杂度和空间复杂度均较低,但是算法复杂,不在掌握范围内。文章来源地址https://www.toymoban.com/news/detail-844785.html

到了这里,关于动态规划——最长回文子串的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • day57|动态规划17-最长回文子串问题

    回文子串:强调连续 回文子序列:不强调连续 暴力思路:三层for循环 双指针思路: 动态规划 dp数组 dp[i][j]: 根据字符串的形式和所解决的问题确定dp数组的形式和含义。 递归公式 (分情况讨论) 2.1 如果 i j 2.2 如果 j-i 1 2.3 如果 j-i1: 此时依赖于dp[i+1][j-1]是不是一个回文子

    2024年02月09日
    浏览(42)
  • 【算法】算法学习七:动态规划 | 背包问题 | 最长公共子串(含源代码)

    背包问题是一种经典的组合优化问题,通常有两个版本:0-1背包问题和无限背包问题。 0-1背包问题是指给定一个背包容量和一组物品,每个物品有自己的重量和价值,要求在不超过背包容量的情况下,选择一些物品放入背包,使得物品的总价值最大化。每个物品只能选择放入

    2024年02月09日
    浏览(48)
  • 【算法沉淀】最长回文子串

     🎉🎉欢迎光临🎉🎉 🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀 🌟特别推荐给大家我的最新专栏 《数据结构与算法:初学者入门指南》📘📘 希望能和大家一起学习!共同进步! 这是苏泽的个人主页可以看到我其他的内容哦👇👇 努力的苏泽 http://su

    2024年03月12日
    浏览(45)
  • 算法刷题|647.回文子串、516.最长回文子序列

    题目:给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 d

    2024年02月17日
    浏览(50)
  • 最长子串和回文子串相关的算法题解

    中等 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s = “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 示例 2: 输入: s = “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。 示

    2024年02月19日
    浏览(40)
  • 【算法Hot100系列】最长回文子串

    💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老 导航 檀越剑指大厂系列:全面总

    2024年02月04日
    浏览(37)
  • 动态规划 回文子串

    647. 回文子串 双指针法: 遍历回文中心----一个回文中心----两个回文中心 遍历边界,找出回文中心, 其实还是变相的找回文中心 动态规划: 4. dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false 5. 当s[i]与s[j]不相等,那没

    2024年02月15日
    浏览(43)
  • 最长公共子串(动态规划)

    查找两个字符串a,b中的最长公共子串_牛客题霸_牛客网 1.找a 和 b 的最长公共子串实际上是在a的子串和b的子串中找最长公共子串 ins[i][j]实际上记录的就是 以a的第i个字符和以b的第j个字符结尾的子串中存在的最长公共子串的长度 接下来我们举个例子: a: abcabcde b: abcd ins[1][1]

    2023年04月12日
    浏览(49)
  • 动态规划-最长的回文序列

    该题是lintcode上 667 · 最长的回文序列,该题的解题思路亦是参考了九章侯老师的解题思路给出。 给一字符串 s, 找出在 s 中的最长回文子序列的长度. 你可以假设 s 的最大长度为 1000. 输入: “bbbab” 输出: 4 解释: 一个可能的最长回文序列为 “bbbb” 输入: “bbbbb” 输出:

    2024年02月09日
    浏览(47)
  • 力扣第131题 分割回文串 c++ 回溯+简单 动态规划(是否为回文子串)

    131. 分割回文串 中等 相关标签 字符串   动态规划   回溯 给你一个字符串  s ,请你将   s   分割成一些子串,使每个子串都是  回文串  。返回  s  所有可能的分割方案。 回文串  是正着读和反着读都一样的字符串。 示例 1: 示例 2: 提示: 1 = s.length = 16 s  仅由小写

    2024年02月07日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包