647. 回文子串
【解法1】纯暴力解法,应该是O(n^3),居然AC了:
class Solution {
public:
int countSubstrings(string s) {
// 暴力
int cnt = 0;
cout << s.substr(1,1);
for(int i = 0; i < s.size();i++)
{
for(int j = i; j < s.size();j++)
{
string tmp = s.substr( i , (j-i+1)); // 为O(len)
string reverse1 = tmp;
reverse(tmp.begin(), tmp.end());
if(reverse1 == tmp)cnt++;
}
}
return cnt;
}
};
【解法2】动态规划。我按照经验的dp数组含义写,写不出,看了题解才懂。本题的dp数组和以往的不太一样,要回到回文串的本质,判断回文串的方法——当 [i,j] 确定是一个回文串时候,若i-1和j+1对应的字符一样,则可判断是一个回文串。
详见注释,AC代码:
class Solution {
public:
//int dp[1010]; // dp[i] 以i结尾的连续的字符串是回文子串的最大个数==>组合数目【错误】
bool dp[1010][1010]; // dp[i][j]表示 s[i,j] 是一个回文串
/*
j >= i
if(s[i] != s[j])
dp[i][j] = 0;
else
{
if(i == j) // 一个元素
dp[i][j] = 1;
else
{
if(j == i+1)dp[i][j] = 1; //两个元素
else //至少3个元素
{
if(dp[i+1][j-1])dp[i][j] = 1;
}
}
}
初始化 dp[i][j] = 0;
顺序:
需要左下角是满的
i-- j++
模拟——
*/
int countSubstrings(string s)
{
int ans = 0;
for(int i = s.size()-1; i >= 0; i--)
{
for(int j = i; j < s.size();j++)
{
if(s[i] != s[j])
dp[i][j] = 0;
else
{
if(i == j) // 一个元素
{
dp[i][j] = 1;
ans++;
}
else
{
if(j == i+1)
{
dp[i][j] = 1; //两个元素
ans++;
}
else //至少3个元素
{
if(dp[i+1][j-1])
{
dp[i][j] = 1;
ans++;
}
}
}
}
}
}
return ans;
}
};
【解法3】双指针法。O(n^2)。
首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。
在遍历中心点的时候,要注意中心点有两种情况。
一个元素可以作为中心点,两个元素也可以作为中心点。那么有人同学问了,三个元素还可以做中心点呢。其实三个元素就可以由一个元素左右添加元素得到,四个元素则可以由两个元素左右添加元素得到。所以我们在计算的时候,要注意一个元素为中心点和两个元素为中心点的情况。
AC代码:
class Solution {
public:
int count(string s,int l,int r)
{
int ans = 0;
while(l >= 0 && r < s.size() && s[l] == s[r])
{
l--;
r++;
ans++;
}
return ans;
}
int countSubstrings(string s)
{
int ans = 0;
for(int i = 0; i < s.size();i++)
{
// 以i为单点中心
ans += count(s,i,i);
// 以i为双点中心的左中心
ans += count(s,i,i + 1);
}
return ans;
}
};
5. 最长回文子串 (类似的题)
用中心扩展双指针的做法:
class Solution {
public:
int ans = 0;
int max_l,max_r = 0;
void count(string s,int l,int r)
{
while(l >= 0 && r < s.size() && s[l] == s[r])
{
l--;
r++;
}
l++;
r--;
if(r-l+1 > ans)
{
max_l = l;
max_r = r;
ans = r-l+1 ;
}
}
string longestPalindrome(string s) {
for(int i = 0; i < s.size();i++)
{
// 以i为单点中心
count(s,i,i);
// 以i为双点中心的左中心
count(s,i,i + 1);
}
return s.substr(max_l,max_r - max_l + 1);
}
};
516. 最长回文子序列
我在学习完上一题的基础上,直接尝试推导这一题,因为我错误的dp数组含义:以i开头 j结尾的回文子序列的最大长度。结果状态转移方程弄的很复杂。还嵌套两个for循环。应该是能过很多测试点的,在第65个测试点 TLE 了。代码如下:
class Solution {
public:
int dp[1010][1010]; // 以i开头 j结尾的回文子序列的最大长度
/*
if(s[i] != s[j])dp[i][j] = 0
else
{
if(i == j)dp[i][j] = 1; //一个元素
else if(j == i+1)dp[i][j] = 2; //两个元素
else //至少三个元素
{
int tmp = 0;
for(int k1 = i+1; k1 <= j-1; k1++)
{
for(int k2 = k1; k2<= j-1; k2++)
{
tmp = max(tmp,dp[k1][k2]);
}
}
if(tmp == 0)dp[i][j] = 0;
else dp[i][j] = tmp+2;
}
}
初始化:
默认为0
顺序:
i--j++
模拟:
*/
int longestPalindromeSubseq(string s)
{
int ans = 0;
for(int i = s.size()-1; i >= 0; i--)
{
for(int j = i; j < s.size();j++)
{
if(s[i] != s[j])dp[i][j] = 0;
else
{
if(i == j)dp[i][j] = 1; //一个元素
else if(j == i+1)dp[i][j] = 2; //两个元素
else //至少三个元素
{
int tmp = 0;
for(int k1 = i+1; k1 <= j-1; k1++)
for(int k2 = k1; k2<= j-1; k2++)
tmp = max(tmp,dp[k1][k2]);
if(tmp == 0)dp[i][j] = 0;
else dp[i][j] = tmp+2;
}
}
ans = max(ans,dp[i][j]);
}
}
return ans;
}
};
学习完题解,要把dp数组的含义改成:以[i,j]中的回文子序列的最大长度。然后状态转移方程就比较好推导了。
AC代码:
class Solution {
public:
int dp[1010][1010]; // 以[i,j]中的回文子序列的最大长度
/*
if(s[i] == s[j])dp[i][j] = dp[i+1][j-1] + 2;
else
{
dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
去掉j元素 去掉i元素
}
初始化:
默认为0,所以正对角线上全设置为1 ,便于状态转移的j循环从i+1开始
顺序:
i--j++
模拟:
*/
int longestPalindromeSubseq(string s)
{
int ans = 1;
for(int i = 0; i < s.size();i++)dp[i][i] = 1;
for(int i = s.size()-1; i >= 0; i--)
{
for(int j = i+1; j < s.size();j++)
{
if(s[i] == s[j])dp[i][j] = dp[i+1][j-1] + 2;
else
dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
ans = max(ans,dp[i][j]);
}
}
return ans;
}
};
go to