题目地址:
https://www.lintcode.com/problem/distinct-subsequences-ii/description
给定一个长 n n n的字符串 s s s,问其所有不同的非空子序列一共多少个。
思路是动态规划。容易想到按照子序列的结尾分类,这里的关键是如何去重。我们先写出
f
f
f的递推式,然后解释其正确性:
f
[
i
]
=
1
+
∑
s
[
j
]
≠
s
[
i
]
,
j
=
0
,
1
,
.
.
.
,
i
−
1
f
[
j
]
f[i]=1+\sum_{s[j]\ne s[i],j=0,1,...,i-1}f[j]
f[i]=1+s[j]=s[i],j=0,1,...,i−1∑f[j]最后答案就是
∑
f
\sum f
∑f。这里累加的时候,略过了与
s
[
i
]
s[i]
s[i]相等的字符对应的
f
f
f。如果
∄
j
<
i
,
s
[
j
]
=
s
[
i
]
\nexists j<i,s[j]=s[i]
∄j<i,s[j]=s[i],那
f
[
i
]
=
1
f[i]=1
f[i]=1,这里的
1
1
1计数的就是
s
[
0
:
i
]
s[0:i]
s[0:i]这个字符串;设
s
[
i
]
s[i]
s[i]在
s
[
0
:
i
−
1
]
s[0:i-1]
s[0:i−1]出现的所有位置分别是
j
1
,
.
.
.
,
j
k
j_1,...,j_k
j1,...,jk且
j
k
<
.
.
.
<
j
1
j_k<...<j_1
jk<...<j1,那么对于
j
1
<
k
<
i
j_1<k<i
j1<k<i,累加
f
[
k
]
f[k]
f[k]所计数的子序列就是
f
[
k
]
f[k]
f[k]所计数的子序列后面接上
s
[
i
]
s[i]
s[i],而对于
j
2
<
k
<
j
1
j_2<k<j_1
j2<k<j1,累加
f
[
k
]
f[k]
f[k]所计数的子序列就是
f
[
k
]
f[k]
f[k]所计数的子序列后面接上
s
[
i
]
×
2
s[i]\times 2
s[i]×2,以此类推。举例如下:对于
s
=
a
b
a
b
s=abab
s=abab,那么
f
[
0
]
=
1
f[0]=1
f[0]=1对应的是
a
a
a,
f
[
1
]
=
2
f[1]=2
f[1]=2对应的是
b
b
b和
a
b
ab
ab,而
f
[
2
]
=
1
+
f
[
1
]
=
3
f[2]=1+f[1]=3
f[2]=1+f[1]=3对应,这里
1
1
1指的是
a
a
aa
aa,而
f
[
1
]
f[1]
f[1]累加的是
b
a
ba
ba和
a
b
a
aba
aba,接着
f
[
3
]
=
1
+
f
[
2
]
+
f
[
0
]
=
5
f[3]=1+f[2]+f[0]=5
f[3]=1+f[2]+f[0]=5,这里
1
1
1指的是
b
b
bb
bb,而
f
[
2
]
f[2]
f[2]累加的是之前
f
[
2
]
f[2]
f[2]所代表的
a
a
aa
aa、
b
a
ba
ba和
a
b
a
aba
aba后面接上
s
[
3
]
=
b
s[3]=b
s[3]=b,即
a
a
b
aab
aab、
b
a
b
bab
bab和
a
b
a
b
abab
abab,而
f
[
0
]
f[0]
f[0]累加的是之前
f
[
0
]
f[0]
f[0]所代表的
a
a
a后面接上
b
×
2
b\times 2
b×2,即
a
b
b
abb
abb。列在下面:
f
[
0
]
f[0]
f[0]:
a
a
a
f
[
1
]
f[1]
f[1]:
b
b
b;
a
b
ab
ab
f
[
2
]
f[2]
f[2]:
a
a
aa
aa;
b
a
ba
ba,
a
b
a
aba
aba
f
[
3
]
f[3]
f[3]:
b
b
bb
bb;
a
a
b
aab
aab,
b
a
b
bab
bab,
a
b
a
b
abab
abab;
a
b
b
abb
abb
最后答案就是
11
11
11。
我们证明一下上面的算法的正确性:
首先容易看出,任何一个子序列一定会被枚举到(这里可以这样看,任何一个子序列都会在它第一次出现的位置被枚举到,这里的“第一次”出现指的是每个字符都取尽量左边的那个)。接下来只需要证明没有重复即可。这一点可以用数学归纳法证明。对 s s s的长度做归纳。我们要证明的结论是,每个子序列会在它第一次出现的位置被枚举到(这句话的意思是它第一次出现的时候,它的最后一个字符所在位置 i i i对应的 f [ i ] f[i] f[i]会对其进行计数),并且之后不会重复枚举。当长度为 1 , 2 1,2 1,2时结论正确。设长度小于 n n n的情况下结论也正确,当长度等于 n n n的时候。如果有重复,接下来分类讨论。如果最后一个重复的位置是相同的,由归纳假设,前面部分只会在第一次出现的时候枚举到,这就矛盾了;如果最后一个重复的位置不同,一个在 c = s [ n − 1 ] c=s[n-1] c=s[n−1],另一个在 c = s [ m ] c=s[m] c=s[m]并且 m < n − 1 m<n-1 m<n−1,那么将两个子序列同时去掉最后一个字符,前面的部分,由归纳假设,都是在第一次出现的位置被枚举的,但是前面部分的最后一个字符的下标是小于 m m m的,在算 f [ n − 1 ] f[n-1] f[n−1]的时候枚举不到这个子序列,因为算 f [ n − 1 ] f[n-1] f[n−1]的时候枚举到的子序列事实上是上面子序列去掉最后一个字符后后面至少接两个 c c c,这就矛盾了。所以结论正确。
事实上上面的 f [ i ] f[i] f[i]存的就是,所有以 s [ i ] s[i] s[i]结尾的,并且是第一次出现的子序列的个数。
代码如下:
public class Solution {
/**
* @param S: The string s
* @return: The number of distinct, non-empty subsequences of S.
*/
public int distinctSubseqII(String S) {
// Write your code here
int res = 0, MOD = (int) (1E9 + 7);
int[] dp = new int[S.length()];
for (int i = 0; i < S.length(); i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (S.charAt(j) != S.charAt(i)) {
dp[i] += dp[j];
dp[i] %= MOD;
}
}
}
for (int i : dp) {
res += i;
res %= MOD;
}
return res;
}
}
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间 O ( n ) O(n) O(n)。