LeetCode5-最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
一、思路
(一)直觉想法
由于回文串的特点,左右对称,所以考虑设置两个指针分别指向起始位置和最后一个字符的位置,开始匹配
具体做法:设置左指针和右指针
(1)固定左指针不动,右指针一直向左移动
只有当左指针的字符与右指针的字符相同时,才会向右移动
如果某一次匹配中,左指针没有移动,则可以将这个子串舍弃(认为不是回文串)
(2)当左指针的位置>=右指针的位置时,算法停止
为了避免遗漏的现象,设置两个循环:
- 外循环:设置左指针的初始位置
- 内循环:将两个指针所指字符进行匹配
(二)改进方法(一)
在写方法(一)的程序时,卡主了,两个循环不足以实现这个功能,三重循环不予考虑,肯定会超时。
此时,考虑不用字符来匹配,直接采用字符串匹配,此时两个循环可以实现上述功能:
- 外循环:设置左指针的初始位置
- 内循环:将两个指针所指的字符串进行匹配
这一步需要将其中一个字符串逆转
等等,既然都有逆转功能了,为什么不直接逆转整个字符串,再用减法判断字符串逆转前后是否相等呢?
这样一来,整个程序可以大幅简化只需设置两个向量用于保存当前回文串和之前找到的最长回文串
实际上,如何逆转字符串也是一个问题,如果按照一个一个字符来赋值,那么逆转一个字符串的时间复杂度为: O ( n ) O(n) O(n)
这样还是相当于一个三重循环,可以添加一些条件来减少循环的次数
C++代码如下:
class Solution {
public:
string longestPalindrome(string s) {
string reversion, s_len_max, s_len;
int low = 0, high = s.size() - 1;
int i, j, len, len_max = 0;
for (i = 0; i <= high; i++) {
// 判断当前字符串的最大长度是否小于已经找到的最长回文串
if ((high - i + 1) <= len_max)
break;
for (j = high; j >= i; j--) {
len = j - i + 1;
// 判断当前字符串长度与已知最长回文串长度的关系
if (len <= len_max)
break;
// 判断首尾是否是同一字符
if (s[i] == s[j]) {
reversion = s.substr(i, len);
reverse(reversion.begin(), reversion.end());
if (reversion == s.substr(i, len)) {
s_len = s.substr(i, len);
s_len_max = s_len;
len_max = s_len_max.size();
}
}
}
}
return s_len_max;
}
};
执行效率:
不愧是三重循环,即使在我优化之后,效率还是“不负众望”,未完待续
(三)动态规划
设置一个向量dp[i][j]来表示当前字符子串s[i]…s[j]中,最长回文串的长度
i与j分别指向当前某个子串的首尾,判断s[i]与s[j]是否相等:
(1)s[i]==s[j]
相等时可以认为回文串长度+2吗?
不行,因为,我们并不知道dp[i + 1][j - 1]是不是一个回文串
因此需要先判断dp[i + 1][j - 1]是不是回文串
- 是,则长度+2
- 不是,那么这次判断等于是无效的,可以当成是两者不等来处理
d p [ i ] [ j ] = m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) dp[i][j]=max(dp[i+1][j],dp[i][j−1])
(2)s[i]!=s[j]
不相等时,可知s[i]…s[j]中最长的回文串应该在其长度减1的子串中,考虑:
- 舍弃s[i],找出s[i+1]…s[j]字符串的最大回文串长度
- 舍弃s[j],找出s[i]…s[j-1]字符串的最大回文串长度
使用上述两种方法找到的回文串长度中,取较大者,就是s[i]…s[j]中最长的回文串的长度
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
+
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
)
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
dp[i][j]=max(dp[i+1][j],dp[i][j−1])
C++代码如下:
class Solution {
public:
string longestPalindrome(string s) {
if (s.empty() || s.size() == 1)
return s;
int start = 0;
int len_max = 0;
vector<vector<int>> dp(s.size(), vector<int>(s.size()));
for (int j = 0; j < s.size(); j++) {
dp[j][j] = 1;
for (int i = j - 1; i >= 0; i--) {
if (s[i] == s[j]) {
if (dp[i + 1][j - 1] == j - i - 1)
dp[i][j] = dp[i + 1][j - 1] + 2;
else
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
}
else
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
if (dp[i][j] > len_max) {
len_max = dp[i][j];
start = i;
}
}
}
return s.substr(start, len_max);
}
};
执行效率:
???为什么反而更慢了
看了下解题方案,说是动态规划也是
O
(
n
2
)
O(n^2)
O(n2)所以。。。。。