字符串的匹配问题很多得依靠DP来解答,在做题的过程中,虽然能用递归完成,但是大都以TLE结束。后来分析了下,主要还是枚举的分支太多,造成枚举负担太大。
题目一:Interleaving String
Given s1, s2, s3, find whether s3 is formed by the iterleaving of s1 and s2.
思路1:这个题如果 用递归的方法解决,得需要用下标分别控制s1, s2, s3, 就像Merge一样。另外,由于这是一道判断是非题,该递归还需要返回值。
public class Solution {
public boolean isInterleave(String s1, String s2, String s) {
return isInterleave(s1, 0, s2, 0, s, 0);
}
public boolean isInterleave(String s1, int i1, String s2, int i2, String s, int i){
if(i>=s.length()){
if(i1>=s1.length() && i2>=s2.length()) return true;
else return false;
}
if(i1 < s1.length() && s1.charAt(i1)==s.charAt(i) && isInterleave(s1,i1+1,s2,i2,s,i+1))
return true;
if(i2 < s2.length() && s2.charAt(i2)==s.charAt(i) && isInterleave(s1,i1,s2,i2+1,s,i+1))
return true;
return false;
}
}
思路2:用一个二维数组DP[i][j]来表示s1当前长度为i, s3当前长度为j, s2当前长度为j-i时是否正确interleaving. (这里也能用i, j 分别表示s1, s2的当前长度)。确定了备忘录,只有得确定递推关系。DP[i][j]的子问题有二个: DP[i][j-1]和DP[i-1][j-1],DP[i][j-1]是只有s2与s3匹配上了,DP[i-1][j-1]表示s1与s3匹配上了。
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
const int n1=s1.length();
const int n2=s2.length();
const int n3=s3.length();
if(n3!=n1+n2) return false;
bool dp[n3+1][n2+1];
for(int i=0; i<=n3; i++)
for(int j=0; j<=n1; j++)
dp[i][j]=false;
dp[0][0]=true;
for(int i=0; i
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
const int n1=s1.length();
const int n2=s2.length();
const int n3=s3.length();
if(n3!=n1+n2) return false;
bool dp[n1+1][n2+1];
for(int i=0; i<=n1; i++)
for(int j=0; j<=n2; j++)
dp[i][j]=false;
dp[0][0]=true;
for(int i=0; i<=n1; i++){
for(int j=0;j<=n2; j++){ //s2的长度在[0, n2]之间
if(dp[i][j]==false) continue;
int k=i+j;
if(s2[j]==s3[k])
dp[i][j+1]=true;
if(s1[i]==s3[k])
dp[i+1][j]=true;
}
}
return dp[n1][n2];
}
};
注意这里利用的后推的形式,个人认为这里后推比前推有优势,其一:s1, s2, s3与DP之间的index对应不需要额外校正,其二,由于递推只有在子问题是true上递推才成立,否则都是白谈,但是这里有两个子问题,后推只有判读一次即可。
题目二:Edit Distance
Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. 3 operations are permitted: insert, delete, replace.
思路:用二维数组dp[i][j]分别表示长度为i的s1转换为长度为j的s2时需要的最小操作数。dp[i][j]有三个子问题,dp[i-1][j], dp[i][j-1]和dp[i-1][j-1], dp[i-1][j]表示insert, dp[i][j-1]表示insert, dp[i-1][j]表示delete,而dp[i-1][j-1]表示replace。
class Solution {
public:
int minDistance(string word1, string word2) {
const int m=word1.length();
const int n=word2.length();
int dp[m+1][n+1];
for(int i=0; i<=m; i++) dp[i][0]=i;
for(int i=0; i<=n; i++) dp[0][i]=i;
for(int i=1; i<=m; i++){
for(int j=1; j<=n; j++){
int minDis=min(dp[i-1][j]+1, dp[i][j-1]+1);
minDis=min(minDis, dp[i-1][j-1]+(word1[i-1]==word2[j-1]?0:1));
dp[i][j]=minDis;
}
}
return dp[m][n];
}
};
题目三:Distinct Subsequences
Given a string S and a strinig T, count the number of distinct subsequences of T in S. [subsequence is not substring!]
思路:这个题大概只能用DP来解了吧,用递归什么的根本无所下下手啊。难题!dp[i][j]表示T(1..i)中不同S(1..j)子序列。子问题有两种情况,当T[i]==S[j]时,继续使用S[j]; 当ST[i]!=S[j], 放弃S[j], S继续先前循环。
class Solution {
public:
int numDistinct(string S, string T) {
int n=S.length();
int m=T.length();
if(m>n) return 0;
vector> num(n+1, vector(m+1, 0));
for(int k=0; k<=n; k++)
num[k][0] = 1; // initialization
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
num[i][j]=num[i-1][j]+(S[i-1]==T[j-1]?num[i-1][j-1]:0);
}
}
return num[n][m];
}
};
注意,因为只有更新至有n-1步骤,故改二维数组可以退化为一维数组,但是时间复杂度还是O(mn).
class Solution {
public:
int numDistinct(string S, string T) {
int n=S.length();
int m=T.length();
if(m>n) return 0;
vector num(m+1,0);
num[0]=1;
for(int i=1;i<=n;i++){
for(int j=m;j>0;j--){
num[j]=num[j]+(S[i-1]==T[j-1]?num[j-1]:0);
}
}
return num[m];
}
};