05 力扣热题刷题记录之第5题最长回文子串

系列文章目录

01 力扣热题刷题记录之第1题两数之和
02 力扣热题刷题记录之第2题两数相加
03 力扣热题刷题记录之第3题无重复字符的最长子串
04 力扣热题刷题记录之第4题寻找两个正序数组的中位数


前言

每天进步一点点!!

一、背景

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

示例 1:

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

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/
来源:力扣(LeetCode)

二、我的思路

暴力解决,遍历可能的字符子串,判断是否回文。同时找到长度最大的串。
我的遍历,使用两个for循环,但是依据的是下标来遍历,而不是长度。时间复杂度可能就是o(n^3),本身遍历两个for循环,然后还有回文判断的for循环,但是循环都不是n的量级,接近n?

具体代码:

class Solution {
public:
	//判断是否回文
    bool judgePalindrome(const char *st,int start,int end)
    {
        int i=start;
        int j=end;
        while(i<=j)
        {
            if(st[i]!=st[j])
            {
                return false;
            }
            else
            {
                i++;
                j--;
            }
        }
        return true;
    }
    string longestPalindrome(string s) {
        string answerStr="";
        int max_i=0;//记录最长回文字符子串的末尾下标
        int max_l=0;//记录最长长度
        const char *ch=s.c_str();
        int dp[1005]={0};//表示截止dp[i]这个i下标,最长的回文串
        dp[0]=s.empty()?0:1;//设立初始值
        answerStr=ch[0];
        max_i=0;
        max_l=1;
        for(int i=1;i<s.length();i++)
        {
        	//枚举以i下标为止的字符前所有的字符子串
            for(int j=0;j<i;j++)
            {	
            	//如果是回文串,看看是不是最大的长度,
            	//不是最大的长度无需修改。非回文串,置为1或不变
                if(judgePalindrome(ch,j,i))
                {
                    dp[i]=max(dp[i],i-j+1);
                    if(max_l<dp[i])
                    {
                        max_i=i;
                    }
                    max_l=max(max_l,dp[i]);                 
                    break;//有一个进来了,说明示最长的了,后面无需判断
                }
                else{
                    dp[i]=max(dp[i],1);
                }
            }  
        }
        //这里本来可以用字符串切片函数substr(start.length);
        //但是我不知道,就转为数组,出了很多问题,
        //详情可见博客:https://blog.csdn.net/ksws0292756/article/details/79432329
        char c[1001];
        int ck=-1;
        //printf("%d",max_l);
        for(int k=max_i;k>=max_i-max_l+1;k--)
        {
            ck++;
            c[ck]=ch[k];
            
        }
        c[++ck]='\0';
        answerStr=c;
        return answerStr;
    }
};

字符串与字符数组,这个博客讲的很清楚。
https://blog.csdn.net/ksws0292756/article/details/79432329

三、官方的思路

官方给了好几种答案,这里主要将一二吧。

(1)地推求解dp[i][j]=dp[i+1] [j-1]

第一个,它是这样做的,使用dp[i][j]记录从i到j的字符串是不是回文串(bool值),但是它不需要回文判断函数,而是根据地推的关系。对于长度在3以内的,很容易知道是不是回文。根据这个基本条件,第一层遍历按长度来,也就是短的串是不是回文清楚了,继而求长一个字符或者两个字符的串就很简单了。时间复杂度是o(n ^2);存储动态规划状态需要的空间是为dp数组,故空间复杂度也是o(n ^2)。

核心思路:如果一个字符串是回文串,那么首字母和尾字母也是回文的,去除首尾字母的字符串也是回文。然后代码的思路反过来,如果我判断到子串是回文,只需看首字母和尾字母是否是相等即可。
于是有了递推式:dp[i] [j]=dp[i+1] [j-1]

起初我在想,他怎么会提前知道dp[i+1] [j-1]是不是回文,但是它的第一个循环是长度啊,在计算dp[i] [j]这个串的长度时,dp[i+1] [j-1]的长度早就被计算过了,具体看代码:

class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.length();
        if(n<2)
            return s;

        //dp[i][j]表示从i到J的字符串是不是回文串,是为1,否为0
        vector<vector<int>> dp(n,vector<int>(n));
        //初始化dp数组
        for(int i=0;i<n;i++)
        {
            dp[i][i]=1;
        }

        int maxl=1;
        int begin=0;
        //按长度来遍历字符串的情况
        for(int L=2;L<=n;L++)
        {
            for(int i=0;i<n;i++)
            {
                //L=j-i+1;
                int j=L+i-1;
                //考虑j越界
                if(j>=n) break;
                if(s[i]!=s[j])
                {
                    dp[i][j]=0;
                }
                else
                {
                    if((j-i)<3)//头尾相等,要么是两个字符,要么是三个字符,肯定回文
                    {
                        dp[i][j]=1;
                    }
                    else//超过三个字符,就不能直接判断了,要借助动态规划的办法
                    {
                        //因为头尾相等,所以跟去除头尾的子串是同个回文
                        dp[i][j]=dp[i+1][j-1];
                        //但是怎么知道去除头尾的先算了,因为他们长度小啊,
                        //循环是按长度来走的,所以肯定有结果了
                    }
                }
                //dp计算成功,及时比较,得到最大的长度
                if(dp[i][j]==1&&j-i+1>maxl)
                {
                    maxl=j-i+1;
                    begin=i;
                }
            }       
        }
        return s.substr(begin,maxl);//切片
    }
};

(2)回文中心扩展

对所有的字符进行遍历,把它当做回文串的中心往两边扩展,看看扩展的时候是不是回文串。

发现没有,如果上面那样做的话,针对奇数个数的字符串(回文串)是有效的,因为奇数有唯一的中心;但是针对偶数回文串就不行,那样永远找不到。所以,每次遍历的时候,中心点是一个和两个都得进行。
具体体现:

//expandAroundCenter是扩展函数
auto [left1, right1] = expandAroundCenter(s, i, i);
auto [left2, right2] = expandAroundCenter(s, i, i + 1);

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
来源:力扣(LeetCode)

详细代码:

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

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
来源:力扣(LeetCode)

(3)网友的2ms的java代码

思路可学习!
当出现abbbbbbbbbbbbbac,这样的回文串的时候,按照官方第2的思路对每一个字符当做中心做判断,那就是浪费时间了,因为明显中间一大串b,可以当做一个字母b来做,无需遍历那么多次,只要一次就可以了,该网友就是这样对中心扩展的办法做了优化。
优化的java代码:

 int high = low;
        while (high < str.length - 1 && str[high + 1] == str[low]) {
            high++;
        }
//         定位中间部分的最后一个字符
        int ans = high;
        //ans用于返回主函数的,用于遍历字母的循环那里,
        //直接就跳过了相同字母,而且不浪费时间,
        //如果不是一大串相同字母,立刻就退出了这个循环

该网友的详细java代码:

class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
//         保存起始位置,测试了用数组似乎能比全局变量稍快一点
        int[] range = new int[2];
        char[] str = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
//             把回文看成中间的部分全是同一字符,左右部分相对称
//             找到下一个与当前字符不同的字符
            i = findLongest(str, i, range);
        }
        return s.substring(range[0], range[1] + 1);
    }
    
    public static int findLongest(char[] str, int low, int[] range) {
//         查找中间部分
        int high = low;
        while (high < str.length - 1 && str[high + 1] == str[low]) {
            high++;
        }
//         定位中间部分的最后一个字符
        int ans = high;
//         从中间向左右扩散
        while (low > 0 && high < str.length - 1 && str[low - 1] == str[high + 1]) {
            low--;
            high++;
        }
//         记录最大长度
        if (high - low > range[1] - range[0]) {
            range[0] = low;
            range[1] = high;
        }
        return ans;
    }
}

因为java和C++一些语法是比较像的,当做学习思路啦!!

总结

总体来讲,就是要认识回文串的特性。

暴力就每一次都要判断 i~j 是否是回文,但是呢,如果考虑了回文串子串也是回文,就可以通过地推式减少判断回文这个过程。如果换个思路,不是从起始点到结束点,而是考虑回文的中心向两边扩展,又是更高效的代码。再考虑重复字符多的串,就又可以优化。

喜欢就点个赞再走吧!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值