子串问题
子串问题一般是枚举每个位置作为(开始/结束/中间),然后再整个串中找最大/最小,例如:
回文子串(枚举每一个字符作为中间字符的情况)
无重复子串(枚举每一个字符作为子串的开始字符的情况)
子串问题先考虑双指针(例如滑动窗口),子序列考虑动态规划
经典面试题-最长回文子串
双指针
回文串的的长度可能是奇数,也可能是偶数,这就添加了回文串问题的难度,解决该类问题的核心是双指针
关于使用双指针的问题总结
labuladong
这道题如何使用双指针呢?
寻找回文串的问题核心思想是:从中间开始向两边扩散来判断回文串
遍历字符串中的字符,找以每个字符为中心的最大回文子串,然后再找整个字符串的最大回文子串
for 0 <= i < len(s):
找到以 s[i] 为中心的回文串
找到以 s[i] 和 s[i+1] 为中心的回文串
更新答案
怎么找以s[i](或s[i]和s[i+1])为中心的回文串呢?
string plalindrome(int l,int r,string &str1)
{
string str=str1;
int len=str.size();
while(l>=0&&r<len&&s[l]==s[r])//因为子串必须是连续的
{
l--;r++;//向两边展开
}
return str.substr(l+1,r-l-1);//因为是l--和r++过的
}
所以整体的代码如下:
string fun(string &ss)
{
string s=ss;
string res;
for(int i=0;i<s.size();i++)
{
s1=plalindrome(i,i,s);
s2=plalindrome(i,i+1,s);
res=s1.size()>s2.size()?s1:s2;
}
return res;
}
动态规划
动态规划也可以解决,时间复杂度一样,但是空间复杂度为
O
(
n
2
)
O(n^2)
O(n2) 存储dp[][]
二维数组dp[i][j]=1表示s[i…j]是最长回文子串,dp[i][j]=0表示不是,那么如果dp[i][j]=1那么dp[i+1][j-1]也应该等于1,否则dp[i][j]将不是回文串所以
状态转移方程为:
base case:
dp[i][i]=1
dp[i][i+1]=1 if str[i]==str[i+1]
还有一个字符和两个相同字符的子串都是回文串
用双指针解决的时候,遍历字符串是枚举回文串的中心字符,这里是遍历字符串枚举回文串的起始字符。
string longestplatindrome(stirng s)
{
int start,len;//回文串的开始和长度
//base case
for(int i=0;i<s.size();i++)
{
dp[i][i]=1;start=i;len=1;
if(i<len-1&&s[i]==s[i+1])dp[i][i+1])dp[i][i+1]=1;
{
start=i;
len=2;
}
}
//枚举回文串的开始字符
for(int l=3;l<=s.size();l++)
for(int i=0;i+l-1<s.size();i++)
{
if(s[i]==s[i+l-1]&&dp[i+1][i+l-2]=1)
{
len=l;
start=i;
}
}
return s.substr(i,len);
}
最长回文子序列
首先明确,
子序列和子串的区别:
子串必须是连续的,子序列不要求是连续的
所以最长回文字序列中,如果用动态规划,if(dp[i+1][j-1]=0)即不是回文子串,那么dp[i][j]一定也不是回文子串,通过这个,就可以从base case通过枚举所有子串起始位置来得到最长子串;当然,子串用双指针更简单,从中间向两侧延伸,遍历子串的中间字符(一个或者两个)
子序列可以是不连续的,思路就像最长上升子序列靠拢,但因为他是回文,设计两侧对称,因此考虑用一个二维数组去存放dp table
dp[i][j]表示是s[i…j]的最长回文子序列
base case:一个字符的肯定是回文子序列,同时,如果i>j那么不存在回文子序列。所以:
dp[i][i]=1
dp[i][j]=0 (i>j)
状态转移方程:对于dp[i][j]怎么求呢?
已知dp[i][i]那么,dp[i-1][i+1]?如果s[i-1]=s[i+1],那么s[i-1][i+1]就是长度为3的回文子序列,按照这个思路类推,dp[i][j]可以从dp[i+1][j-1]推理,
s[i]=s[j],dp[i][j]=dp[i+1][j-1]+2,
s[i]!=s[j],s[i]和s[j]分别加入s[i+1…j-1],dp[i][j]=max(dp[i][j-1],dp[i+1][j])