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
官方给了好几种答案,这里主要将一二吧。
第一个,它是这样做的,使用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);//切片
}
};
对所有的字符进行遍历,把它当做回文串的中心往两边扩展,看看扩展的时候是不是回文串。
发现没有,如果上面那样做的话,针对奇数个数的字符串(回文串)是有效的,因为奇数有唯一的中心;但是针对偶数回文串就不行,那样永远找不到。所以,每次遍历的时候,中心点是一个和两个都得进行。
具体体现:
//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)
思路可学习!
当出现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 是否是回文,但是呢,如果考虑了回文串子串也是回文,就可以通过地推式减少判断回文这个过程。如果换个思路,不是从起始点到结束点,而是考虑回文的中心向两边扩展,又是更高效的代码。再考虑重复字符多的串,就又可以优化。
喜欢就点个赞再走吧!!