Distinct Subsequences
题目连接:https://oj.leetcode.com/problems/distinct-subsequences/
runtimes:38ms
一、问题
Given a string S and a string T, count the number of distinct subsequences of T in S.
A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not).
Here is an example:
S = "rabbbit", T = "rabbit"
Return 3.
二、分析
如题所示,题目要求求出字符串S有多少个与字符串T相同的字符串。传统思路是用回溯法,首先比较S[i]和T[i],如果相等,那么就继续比较S[i + 1]和T[i + 1];否则比较S[i + 1]和T[i],依次类推,直到扫描完T计数一次,扫描完S表明不存在相同序列。但是酱紫时间和空间消耗都很大,一般来说跑步过去。因此要另寻蹊径。
想起以前算法教过的求两个字符串的最长子字符串,那么这道题应该相似,用动态规划的思想。
如上图,以S = “rabbrabitbt”,T = "rabbit"为例,开一张表record[][]记录检查过的字符,递推关系如下:
如果S[j] == T[i],record[i][j] = record[i][j - 1] + record[i - 1][j - 1]
如果S[j] != T[i],record[i][j] = record[i][j - 1]
最终扫描结束,得到record[T.size() - 1][S.size() - 1]为所求结果。
凡事有个为什么。首先T字符串每一个字符为一个阶段,该阶段reocrd[i][j]的决策受上个阶段的结果影响,record[i][j - 1]表示T[i]已经和S的前j - 1个字符串比较过了,如果不同,说明之前T[i]与S的前j个字符串至少有record[i][j - 1]个相同;如果相同,需要看上阶段字符record[i - 1][j - 1]的情况。
有如下规定:
假如T为空,则record[0][1...S.size() - 1]都为1,表示T和S只有一个子序列空相同;
假如S为空,则record[1...T.size() - 1]都为零。
三、小结
用一张二维表记录状态,递推公式如下:
如果S[j] == T[i],record[i][j] = record[i][j - 1] + record[i - 1][j - 1]
如果S[j] != T[i],record[i][j] = record[i][j - 1]
四、实现
回溯方案的实现:
class Solution {
public:
void find(string S, string T, int sk, int tk, int &counter)
{
if (tk < T.size())
{
for (int i = sk; i < S.size(); i++)
{
if (S[i] == T[tk])
{
find(S, T, i + 1, tk + 1, counter);
}
}
}
else{
counter++;
}
}
void find2(vector <vector <int> > v, int t, int k, int n, int &counter)
{
if (k < n)
{
for (int i = 0; i < v[k].size(); i++)
{
if (t < v[k][i])
{
find2(v, v[k][i], k + 1, n, counter);
}
}
}
else{
counter++;
}
}
int numDistinct(string S, string T) {
int c = 0;
vector <vector <int> > vec;
for (int i = 0; i < T.size(); i++)
{
for (int j = 0; j < S.size(); j++)
{
if (T[i] == S[j])
{
if (i >((int)vec.size() - 1))
{
vector <int> v;
v.push_back(j);
vec.push_back(v);
}
else{
vec[i].push_back(j);
}
}
}
}
find2(vec, -1, 0, vec.size(), c);
cout << c;
return c;
}
};
动态规划方案的实现:
class Solution {
public:
int numDistinct(string S, string T) {
vector <vector <int> > record(T.size() + 1, vector <int>(S.size() + 1, 0));
for (int j = 0; j <= T.size(); j++)
record[j][0] = 0;
for (int i = 0; i <= S.size(); i++)
record[0][i] = 1;
for (int i = 1; i <= T.size(); i++)
{
for (int j = 1; j <= S.size(); j++)
{
record[i][j] = T[i - 1] == S[j - 1] ? record[i][j - 1] + record[i - 1][j - 1] : record[i][j - 1];
}
}
return record[T.size()][S.size()];
}
};
五、三思
这个算法的思想参考了以下文章,虽然自己知道最长子序列问题,但是还是不能够灵活的转换运用,动态规划确实比较难,灵活性高,只能多实践多认识。其次发现leetcode的数据有时候会比较长,那么用vector作为数组就方便多了,不会出现奇葩的错误。加油!
原文链接:http://blog.csdn.net/abcbc/article/details/8978146