Distinct Distance
给定字符串S和T,求S中T的子串的个数。这里的子串是指不连续的子串。比如S=”rabbbit”,T=”rabbit”,那么S有三个rabbit子串。(中间的3个b有3种取2个b的取法)
思路
这一是一道动态规划的题目,我们维护的状态变量是S中前i个字符中T的前j个字符的个数dp[i][j],接下来的目标就是寻找递归关系。递推关系大部分都是dp[i][j]和dp[i-1][j-1],dp[i-1][j],dp[i][j-1]等这些关系。
首先把目光集中在了S中第i和字符和T中第j个字符的关系身上。如果S[i-1] != T[j-1],那么dp[i][j] = dp[i-1][j],意思就是如果在S[0:i-2]和T[0:j-1]满足题意的取法在S[0:i-1]和T[0:j-1]仍然满足。
可以理解为即使去掉S[i-1]这个字符也并不影响结果。如果S[i-1]==T[j-1],那么可以理解为dp[i-1][j-1]中的序列也仍然满足dp[i][j],此时dp[i][j] = dp[i-1][j]+dp[i-1][j-1]。
综合上述两种情况,dp[i][j] = dp[i-1][j] + (S[i-1]==T[j-1]? dp[i-1][j-1] : 0)
ps:可以自己举一个例子,然后画出如下二维矩阵,结合矩阵进行递推公式的寻找
Ø r a b b b i t
Ø 1 1 1 1 1 1 1 1
r 0 1 1 1 1 1 1 1
a 0 0 1 1 1 1 1 1
b 0 0 0 1 2 3 3 3
b 0 0 0 0 1 3 3 3
i 0 0 0 0 0 0 3 3
t 0 0 0 0 0 0 0 3
注意
边界条件。如果S为空,dp[0][j] = 0(j > 0),如果T为空,dp[i][0] = 1。
题目中要求了空间复杂度的优化,这在最基本的背包问题曾经了解过,意思就是倒序遍历。
此时公式简化为dp[j] = dp[j] + (S[i-1]==T[j-1] ? dp[j-1] : 0)。
倒序遍历保证在访问dp[j]时,dp[j],dp[j-1]还代表dp[i-1][j],dp[i-1][j-1]。而如果顺序遍历的话,dp[j-1]早于dp[j]被修改,代表的就不是dp[i-1][j-1]了。
代码
int numDistinct(string &S, string &T) {
// write your code here
if (T == "") return 1;
int lens = S.size(), lent = T.size();
vector<int> dp(lent+1, 0);
dp[0] = 1;
for (int i = 1; i < lens+1; i++) {
for (int j = lent; j >= 1; j--) {
dp[j] = dp[j] + (S[i-1] == T[j-1] ? dp[j-1]: 0);
}
}
return dp[lent];
}
Edit Distance
编辑距离,具体概念请百度,这是一种用来衡量字符串距离的方式。
求给定两个字符串的编辑距离。
思路
也是动态规划的角度,维护的状态变量是dp[i][j],代表word1的前i个字符和word2的前j个字符的最小编辑距离。
同样可以先画如下矩阵用来方便理解
Ø a b c d
Ø 0 1 2 3 4
b 1 1 1 2 3
b 2 2 1 2 3
c 3 3 2 1 2
同样把目光放在word1[i-1]和word2[j-1]上,如果word1[i-1] == word2[j-1],那么这两个字符便不需要编辑,此时dp[i][j]=dp[i-1][j-1]。如果word1[i-1] != word2[j-1],那么正如题意中说的,考虑前面的状态如何跳转可以达到该状态。题目中说有3种编辑方法。即如果在word1中插入一个字符串使得两者相等,即dp[i-1][j]+1,如果在word2中插入一个字符使得两者相等,即dp[i][j-1],以及替换一个字符达到该状态,即dp[i-1][j-1]。所谓动态规划,意思就是动着规划,没有固定的套路,每次都是选择最优的套路,即
dp[i][j] = min(dp[i-1][j], min(dp[i-1][j-1], dp[i][j-1])) + 1。
代码
int minDistance(string word1, string word2) {
// write your code here
int len1 = word1.size(), len2 = word2.size();
vector<vector<int>> dp(len1+1, vector<int>(len2+1, 0));
for (int i = 0; i < len1+1; i++)
dp[i][0] = i;
for (int j = 0; j < len2+1; j++)
dp[0][j] = j;
for (int i = 1; i < len1+1; i++) {
for (int j = 1; j < len2+1; j++) {
if (word1[i-1] == word2[j-1]) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1])) + 1;
}
}
}
return dp[len1][len2];
}
Edit DistanceII
上一题的变种,给定两个字符串,问他们之间的编辑距离是不是小于1。
思路
直接求编辑距离然后判断当然是可以,但是此题有更简洁的做法。
首先先比较两个字符串的长度
1.如果长度之差大于1,那么编辑距离显然大于1。
2.如果长度之差等于0,那么只需要遍历一遍,比较相同位置的字符,只有有一个位置不同才满足题意。
3.如果长度之差等于1,那么顺序遍历一遍,如果遍历到两个字符不一样,那么长的那个就去掉该字符串,然后从新比较。
代码
bool isOneEditDistance(string& s, string& t) {
// Write your code here
if (s.size() < t.size()) swap(s, t);
int m = s.size(), n = t.size(), diff = m-n;
if (diff > 1) return false;
if (diff == 1) {
for (int i = 0; i < n; i++) {
if (s[i] != t[i]) {
return s.substr(i+1) == t.substr(i);
}
}
return true;
}
int cnt = 0;
for (int i = 0; i < n; i++) {
if (s[i] != t[i]) cnt++;
}
return cnt == 1;
}
可以经过修改让代码变简洁
bool isOneEditDistance(string s, string t) {
for (int i = 0; i < min(s.size(), t.size()); ++i) {
if (s[i] != t[i]) {
if (s.size() == t.size()) return s.substr(i + 1) == t.substr(i + 1);
else if (s.size() < t.size()) return s.substr(i) == t.substr(i + 1);
else return s.substr(i + 1) == t.substr(i);
}
}
return abs(s.size() - t.size()) == 1;
}