最长回文子串

题目

原题链接


最长回文子串_第1张图片

方法一(暴力破解)

暴力破解应该是最好想到的,没什么思维难度,这里我就不赘述了。直接上代码

class Solution {
public:
    string longestPalindrome(string s) {
        string res="";//存放结果
        string temp="";//存放子串
        for(int i=0;i<s.length();i++)
        {
            for(int j=i;j<s.length();j++)
            {
                temp=temp+s[j];
                string tem=temp;//tem存放子串反转结果
                std::reverse(tem.begin(),tem.end());//反转
                if(temp==tem)
                    res=res.length()>temp.length()?res:temp;
            }
            temp="";
        }
        return res;
    }
};

方法二(动态规划)

思路:

  1. 首先,我们知道如果一个字符串是回文串且长度大于二的话,那么把它的首位字母去掉,它还是回文串。例如,如果s[i~j](这表示字符串i~j组成的字串)这一部分是回文串,并且j-i+1>2,那么s[i+1~j-1]也还是回文串。
  2. 按照这样的思路,我们可以用动态规划来做这道题目。我们定义f[i][j]表示字符串s中从ij组成的字串是否为字符串。我们用1表示是,0表示不是。
  3. 状态转移,就有两种情况(这里先只考虑长度大于二的回文串即j-i+1>2):
    • 如果s[i]=s[j],那么f[i][j]=f[i+1][j-1]
    • 如果s[i]!=s[j[,那么f[i][j]=0
  4. 考虑边界和初始情况:
    • 对于每一个字符,它肯定是回文串,那么f[i][i]=1
    • 对于长度等于的字符串,如果s[i]=s[i+1],那么它也是回文串,即f[i][i+1]=(s[i]==s[i+1])
  5. 根据上面的思路,我们就可以完成动态规划了,最后的答案也是在所有f[i][j]=1中,长度最长的那一个字串。

代码

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 
                {
                    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);
    }
};

注意点

  • 在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串转移的,所以一定要注意循环的顺序,不要写成下面这种(为什么我特别强调呢,因为我一开始就写成这样了,debug了半天 )
        for(int i=0;i<n;i++)
        {
            for(int j=i+1;j<n;j++)
            {
            
            }
        }
  • 首先要先判断给定的字符串长度是否大于一,如果不是,则直接放回原字符串即可。
  • 一定要给maxlenbegin赋初始值,不然会被一些数据卡掉(比如s="ac")。

方法三(中心拓展法)

思路

我们知道每个回文串都会有回文中心,比如acbca的回文中心就是babccba的回文中心就是cc。那么我们就可以列举每一个回文中心,将它向外拓展,然后比较看哪个拓展的长度最长。

代码

class Solution {
public:
    pair<int,int> get(string &s,int l,int r)		//一直向外拓展,获取以l,r为中心的最长回文串
    {
        while(l>=0 and r<s.length() and s[l]==s[r])
        {
            l--;
            r++;
        }
        l++,r--;
        return {l,r};
    }
    string longestPalindrome(string s)
    {
        int res=1,st=0;		//记录最后的结果
        for(int i=0;i<s.length();i++)
        {
            auto [l1,r1]=get(s,i,i);		//第一种情况,回文中心为一个字符的
            auto [l2,r2]=get(s,i,i+1);		//第二次情况,回文中心为两个相邻字符的
            if(res<r1-l1+1)					//取最大值
            {
                res=r1-l1+1;
                st=l1;
            }
            if(res<r2-l2+1)
            {
                res=r2-l2+1;
                st=l2;
            }
        }
        return s.substr(st,res);
    }
};

方法四(Manacher算法)

由于这个算法太复杂,估计写出来也没什么人愿意看,我就不写了。其实我自己也没有搞明白
具体想了解的可以click here


写在最后

如果你觉得我写题解还不错的,请各位王子公主移步到我的其他题解看看

  1. 数据结构与算法部分(还在更新中):
  • C++ STL总结 - 基于算法竞赛(强力推荐
  • 动态规划——01背包问题
  • 动态规划——完全背包问题
  • 动态规划——多重背包问题
  • 动态规划——分组背包问题
  • 动态规划——最长上升子序列(LIS)
  • 二叉树的中序遍历(三种方法)
  • 最短路算法——Dijkstra(C++实现)
  • 最短路算法———Bellman_Ford算法(C++实现)
  • 最短路算法———SPFA算法(C++实现)
  • 最小生成树算法———prim算法(C++实现)
  • 最小生成树算法———Kruskal算法(C++实现)
  • 染色法判断二分图(C++实现)
  1. Linux部分(还在更新中):
  • Linux学习之初识Linux
  • Linux学习之命令行基础操作

✨总结

“种一颗树最好的是十年前,其次就是现在”
所以,
“让我们一起努力吧,去奔赴更高更远的山海”
在这里插入图片描述
如果有错误❌,欢迎指正哟

如果觉得收获满满,可以动动小手,点点赞,支持一下哟

你可能感兴趣的:(数据结构与算法,#,动态规划,leetcode刷题集,动态规划,算法,c++,字符串)