考虑回文字符串的特点,从左往右和从右往左读都是一样的,就是说字符串成了“轴对称”。要求一字符串的最长回文子串,我们可以遍历每个字符,求以该字符为轴对称中心的最长对称子串(或者以该字符和下一个字符为中间两个字符的对称子串),在所有这样的子串中长度最长的那个就是我们要找的答案。从回文字符串的对称性来想——这是一种相对直接的想法。
// C++
class Solution {
public:
pair<int, int> expand(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] = expand(s, i, i);
auto [left2, right2] = expand(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);
}
};
回过头来考虑,对于一个子串,如果它是一个回文字符串,那么去除首尾两个字符(当然前提是该子串的长度大于2)它仍然是一个回文子串,反过来说,对于一个回文子串,如果它前后的两个字符相同,那么把这两个新字符加上所形成的新子串也一定是个回文子串。从动态规划的角度去想,我们就找到了这个问题的状态转移方程。如果使用\(P(i,j)\)表示字符串s
的子串\(s[i:j]\)是否是回文子串,则有:
- \(P(i,j)=true\),\(s[i:j]\)是回文子串
- \(P(i,j)=false\),不是
我们可以写出状态转移方程:\(P(i,j)=P(i+1,j-1) \land (S_i == S_j)\)。显然,这些情况的讨论都是基于子串长度大于2的前提之上,动态规划的边界条件是我们必须考虑的,子串长度为1时,子串一定是回文串,子串长度为2时,当且仅当两个字符相同时子串是个回文串。实际编程时,我们可以通过子串长度和子串起始位置来进行循环。
// C++
class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
if (len < 2) {
return s;
}
// dp[i][j]表示子串[i...j]是否是回文串
vector<vector<int>> dp(len, vector<int>(len));
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
int begin = 0, max_len = 1;
for (int L = 2; L <= len; L++) {
for (int i = 0; i < len; i++) {
int j = i + L - 1;
if (j >= len) {
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];
}
if (dp[i][j] && L > max_len) {
max_len = L;
begin = i;
}
}
}
return s.substr(begin, max_len);
}
};
官方题解中提到,从动态规划法的状态转移方程中可以看出,所有状态在状态转移时的可能性都是唯一的,就是说所有状态都是从某个边界状态扩展得到的,这种所谓边界状态就是子串长度为1或2的状态,从这个角度看,可以得到最开始描述的“中心扩展算法”。
标签:子串,int,len,start,Hot,100,LeetCode,dp,回文 From: https://www.cnblogs.com/louistang0524/p/18417159