题目: 647. 回文子串
标签:双指针 字符串 动态规划
题目信息:
思路一:暴力实现
我们直接for套for分割成一个个子串再判断,如果子串是回文子串,就+1,最后得出结果
代码实现:
class Solution {
public:
bool isH(string a){//判断是不是回文串
int n = a.size();
if(n==0){
return false;
}
for(int i=0,j=n-1;i<j;i++,j--){
if(a[i]!=a[j]){
return false;
}
}
return true;
}
int countSubstrings(string s) {
int n = s.size();
int ans = 0;
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
string ts = s.substr(i,j-i+1);
// cout<<ts<<",";
if(isH(ts)){
ans++;
}
}
}
return ans;
}
};
时间复杂度分析:
遍历的时候是for套for,判断的时候又是套一层for,三重for O(n^3)
思路二:双指针
双指针是一种怎么样的思想呢?
我目前看来,就是在一层for循环中通过两个指针完成两层for循环下才能完成的工作。
这道题也就可以用双指针。在这个题的暴力做法里面,我们不是有分割子串操作吗?我们分割子串这个操作有没有什么方法可以优化一下?
还真有。既然是回文串,当然就要满足两边相等。所以,在回文串中,同时在两端加入一个相同的字符后,新构成的字符串不也还是一个回文串吗?
所以我们可以在遍历字符串的时候,以其中某个字符或者某两个为中心点,向两边扩散构成回文串。
代码实现:
class Solution {
public:
int extend(string s,int i,int j,int n){//向两边扩散,在扩散的同时判断是否是回文子串并记录
int res = 0;
while(i>=0&&j<n&&s[i]==s[j]){
i--;
j++;
res++;
}
return res;
}
int countSubstrings(string s) {
int ans = 0;
//双指针
int n = s.size();
for(int i=0;i<n;i++){
//从中心区开始扩散,
//以为回文串的长度要么是偶数要么是奇数
//所以我们要么一个点为中心,要么两个点为中心
ans+=extend(s,i,i,n);
ans+=extend(s,i,i+1,n);
}
return ans;
}
};
时间复杂度分析:
O(n^2)
思路三: 动态规划
一个回文串去掉两端一个字符后仍然是一个回文串。
这一道题可以用动态规划来做。
第一步:确定dp数组及其下标含义
dp[i][j] i,j对应的是下标,dp[i][j]则是[i,j]这一段子串是不是回文
第二步:确定递推公式
回文的条件是从左到右,从右到左都是一样的,判断方法则是遍历的时从左右两边同时开始,若都相同则为回文,有不同就不是回文。
当首尾相同时,就会出现三种情况,
1.单独一个字符,即本身。
2.两个字符。
3.两个相同字符之间夹了一个回文串。
if(s[i]==s[j]):
情况1,2:j-i<=1。dp[i][j]=true
情况3:判断中间夹的字符串是不是回文串。dp[i+1][j-1] == true?dp[i][j]=true: continue
第三步:初始化dp数组
第四步:遍历,填充dp数组
遍历的顺序就取决于递推公式。
由上面可以得知,判断dp[i][j]的情况时要知道dp[i+1][j-1],所以先要知道i大的,j小的
于是遍历顺序为:
for(int i=len-1;i>=0;i–){
for(int j=i;j<len;j++){
第五步:检验,得出结果
代码实现:
class Solution {
public:
int countSubstrings(string s) {
int len = s.size();
vector<vector<bool>>dp(len,vector<bool>(len,false));
int ans = 0;
for(int i=len-1;i>=0;i--){
for(int j=i;j<len;j++){
if(s[i]==s[j]){
if(j-i<=1){
dp[i][j] = true;
ans++;
}
else if(dp[i+1][j-1]){
dp[i][j] = true;
ans++;
}
}
}
}
return ans;
}
};
时间复杂度分析:
O(n^2)
总结:
好难,看不出来动态规划做法。
总结不出来什么东西啊。