Given a string S
, count the number of distinct, non-empty subsequences of S
.
Since the result may be large, return the answer modulo 10^9 + 7
.
Example 1:
Input: "abc"
Output: 7
Explanation: The 7 distinct subsequences are "a", "b", "c", "ab", "ac", "bc", and "abc".
Example 2:
Input: "aba"
Output: 6 Explanation: The 6 distinct subsequences are "a", "b", "ab", "ba", "aa" and "aba".
Example 3:
Input: "aaa"
Output: 3 Explanation: The 3 distinct subsequences are "a", "aa" and "aaa".
Note:
S
contains only lowercase letters.1 <= S.length <= 2000
這道題是之前那道 [Distinct Subsequences](http://www.cnblogs.com/grandyang/p/4294105.html) 的類似題目,這里只有一個字符串,讓找出所有不同的子序列,如果字符串中沒有重復字符,可以直接得到子序列的個數,但是這里由於重復字符的存在,就大大增加了難度。由於題目中提示了結果可能非常大,要對一個超大數取余,就相當於明確說了要用動態規划 Dynamic Programming 來做,下面就要來考慮 dp 數組的定義和狀態轉移方程的推導了。剛開始博主也是考慮用一個一維數組 dp,其中 dp[i] 表示以 S[i] 結尾的不同子序列的個數,就像 [這個帖子](https://leetcode.com/problems/distinct-subsequences-ii/discuss/192030/Java-DP-O\(N2\)-time-greater-O\(N\)-time-greater-O\(1\)-space) 中定義的一樣,但是狀態轉移方程不好推導,那個帖子雖然代碼可以跑通,但是解釋的卻不通,博主也納悶這算是歪打正着么,希望哪位大神來解釋一下。這里還是根據 [lee215 大神的帖子](https://leetcode.com/problems/distinct-subsequences-ii/discuss/192017/C%2B%2BJavaPython-4-lines-O\(N\)-Time-O\(1\)-Space) 來講解吧。這里使用一個大小為 26 的一維數組 dp,其中 dp[i] 表示以字符 i+'a' 結尾的不同子序列的個數,因為題目中限定了只有小寫字母,所以只有 26 個。以 aba 這個例子來分析一下,當遇到開頭的a時,那么以a結尾的子序列只有一個,就是a,當遇到中間的b時,此時知道以b結尾的子序列有2個,分別是 b 和 ab,是怎么得來的呢,其實是空串和a后面分別加個b得來的,此時貌似得到的值和通過 sum(dp)+1 計算的結果相等,再來驗證一下這個成不成立。當遇到末尾的a的時候,那么此時以a結尾的子序列就有4個,分別是 a,aa,ba,aba,是怎么得來的?在這個a加入之前,當前所有的子序列有,a,b,ab,如果再算上一個空串,[],a,b,ab,則在其后面各加上一個b,就可以得到結果了,貌似也符合 sum(dp)+1 的規律,這其實也並不難理解,因為在當前不同序列的基礎上,加上任何一個字符都會得到另一個不同的子序列,后面的加1是為了加上空串的情況,這個就是狀態轉移方程了,最終的結果是把 dp 數組累加起來取余后返回即可,參見代碼如下:
解法一:
class Solution {
public:
int distinctSubseqII(string S) {
int M = 1e9 + 7;
vector<int> dp(26);
for (char c : S) {
dp[c - 'a'] = accumulate(dp.begin(), dp.end(), 1L) % M;
}
return accumulate(dp.begin(), dp.end(), 0L) % M;
}
};
這里還有另一種解法,由熱心網友`一切忘記回憶`提供,博主覺得思路很巧妙,故而收錄進來,特此感謝一下。這種解法是要建立兩個一維的 dp 數組,其中 a[i] 表示以 S[i] 字符結尾的不同子序列個數,b[i] 表示不以 S[i] 字符結尾的不同子序列的個數,初識時 a[0] = 1, b[0] = 0。然后就是來推導狀態轉移方程了,其中 b[i] 比較簡單,因為不用加上 S[i] 字符,其就更新為前一個位置的兩個狀態之和,b[i] = a[i-1] + b[i-1]。關鍵是 a[i] 稍微復雜一些,需要分情況討論一下,假如 S[i] 這個字符之前沒有出現過,那么目前所有出現的情況后面加上這個新的字符都是不同的,另外再加上這個單獨的字符的一種情況,所以更新為 b[i]+1,因為此時的 b[i] 已經更新為不包括 S[i] 的所有不同子序列個數。當 S[i] 在之前出現過的話,那么就有可能出現重復情況,具體重復的數量就是上一次出現這個字符的位置時候的b值。因為如果選擇加上了當前這個字符,那么所有上一次出現這個字符的地方之前的子序列就都不能選擇了,所以要減去上一次的b值,為了快速知道每次的b值,用一個 HashMap 建立每個字符和其對應的b值即可,參見代碼如下:
解法二:
class Solution {
public:
int distinctSubseqII(string S) {
int n = S.size(), res = 0, M = 1e9 + 7;
unordered_map<char, long> m;
vector<long> a(n), b(n);
a[0] = 1;
m[S[0]] = 0;
for (int i = 1; i < n; ++i) {
b[i] = (a[i - 1] + b[i - 1]) % M;
if (!m.count(S[i])) {
a[i] = (b[i] + 1) % M;
} else {
a[i] = (M + b[i] - m[S[i]]) % M;
}
m[S[i]] = b[i];
}
return (a.back() + b.back()) % M;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/940
類似題目:
參考資料:
https://leetcode.com/problems/distinct-subsequences-ii/
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)