1. 程式人生 > >940. Distinct Subsequences II

940. Distinct Subsequences II

題目連結

940. Distinct Subsequences II

題目描述

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

這種題目其實可以類比subset相關題目,但是該題目和subset題目有相似之處也有不同之處。
不同:subset中的元素可以和原集合中的元素順序保持不一致,因此,我們在計算之前可以將原集合進行排序Subset, SubsetII。 但是該題目中,子集合中的元素順序必須和原本集合中的元素順序保持一致,因此,我們不能排序,排序之後再回溯也會超時。
相同: 都可以在之前求出結果的基礎上添加當前元素形成新的集合。注:之前subset這道題我使用的是回溯的方法,因此複雜度是o(2的n次方),但是subset這道題也能不使用回溯,而是使用在之前基礎上新增新元素的形式(藉助set)。

下面兩種方法本質上都是在之前結果的基礎上新增新元素的方式。

方法一

dp[i]表示以S[i]結尾的不同子字串的數量,則對應的方程為:

i的範圍為{0..len}, j的範圍為{0, i}
dp[i] += dp[j]       s[i] != s[j]
dp[i] += 0            s[i]==s[j] //避免重複

以字串abb為例,初始時,每個dp[i]都為1
i = 0時,dp[0] = 1(初始化值),其代表1個字串a
i = 1時,dp[1] += dp[0] , 即dp[1]=2,其代表字串ab, b
i = 2時, dp[2] += dp[0] , 即dp[2] = 2,其代表的字串為bbabb(注:原本應該為dp[0] + dp[1]

的,即代表字串ab, abb, bb, b的,就是將之前算過的所有字串拼接上新的當前字元,但是因為存在重複的,例如dp[1]代表的是ab, b和當前算出4箇中就有2重複,因此,我們就不將和之前相等的字串結果進行相加了)

其實這個方法本質上下文的方法二是一樣的

class Solution {
    public int distinctSubseqII(String S) {
        if(S == null || S.length() == 0) {
        	return 0;
        }
        int mod = 1_000_000_007;
        int[] dp = new int[S.length()];
        Arrays.fill(dp, 1);
        for(int i = 0; i < S.length(); i++) {
        	for(int j = 0; j < i; j++) {
        		if(S.charAt(i) != S.charAt(j)) {
        			dp[i] = (dp[i] + dp[j]) % mod;
        		}
        	}
        }
        int ans = 0;
        for(int i = 0; i < S.length(); i++) {
        	ans = (ans + dp[i]) % mod;
        }
        return ans;
    }
}

方法二

題目說只會有小寫字母出現,因此我們新建陣列endWithChar[26],其含義為:endWithChar[0]表示以字母a結尾的字串個數,endWithChar[1]表示以字母b結尾的字串的個數,依次類推。

我們從頭開始遍歷字串的每個字元,當前我們已經或者的字串個數為N = sum(endWithChar[0] + endWithChar[1] + ... + endWithChar[25]),假設現在新來一個字元c,則在之前字串的末尾加上c之後,會有新的N個字串以c結尾,再加上1(表示單獨的一個c字串),就是當前以c結尾的字串的數量。下面是一個例子。

假設S = abb,初始時,endWithChar的每個元素都為0,下面是遍歷過程:
i = 0, S[i] = a ,endWithChar[a] = sum(endWithChar) + 1 = 1, 代表字串 a
i = 1, S[i] = b, 則endWithChar[b] = sum(endWithChar) + 1 = 2, 代表兩個字串ab, b
i=2, S[i] =b,endWithChar[b] = sum(endWIthChar) + 1 = 1 + 2 + 1 = 4,代表字串ab, abb, bb, b
因此,最後結果為sum(endWithChar) = 1 + 4 = 5,代表字串a, ab, abb, bb, b

class Solution {
    public int distinctSubseqII(String S) {
        if(S == null || S.length() == 0) {
        	return 0;
        }
        int mod = 1_000_000_007;
        int[] endWithChar = new int[26];
        int length = S.length();
        for(int i = 0; i < length; i++) {
        	endWithChar[S.charAt(i) - 'a'] = (getSum(endWithChar, mod) + 1) % mod;
        }
        return getSum(endWithChar, mod);
    }
    
    private int getSum(int[] array, int mod) {
    	int temp = 0;
    	for(int j = 0; j < 26; j++) {
    		temp = (temp + array[j]) % mod; 
    	}
    	return temp;
    }
}