1639. Number of Ways to Form a Target String Given a Dictionary

https://leetcode.com/problems/number-of-ways-to-form-a-target-string-given-a-dictionary/

首先,在組合出 target 的過程,取哪一個 word 其實不重要,重要的是取了第幾個 character。我們可以把 words 裡面的資訊變成 freq[i][j]freq[i][j]爲「words 中,第 i 個 (0 indexed) character 出現 j 這個英文字母的次數」。

例如: words = ["acca","bbbb","caca"],那麼 freq[3][0] 就是「words 中,第 3 個 character 出現 ‘a’ 這個英文字母的次數」,也就是 2。

接下來定義 dp[i][j] 爲 「每一個 word 的前 i 位 (1 indexed) 配出 target 的前 j 位 (1 indexed) 的方法數」。下面文字的部分沒有特別講都是 1 indexed。

對於 dp[i][j]
如果決定在第 i 位選出 target 的第 j 位,那麼方法數就是 dp[i - 1][j - 1] * freq[i - 1][target[j] - 'a'](freq 是 0 indexed)。
如果決定不在第 i 位選出 target 的第 j 位,那麼方法數就是 dp[i - 1][j]

例如: words = ["acca","bbbb","caca"], target = "aba"dp[3][2] 等於在問 words = ["acc","bbb","cac"], target = "ab" 的方法數。
如果決定在第 3 位選出 target 的第 2 位(也就是 ‘b’),那麼有 dp[2][1] * 1(因爲只有 "bbb" 的第 3 位是 'b') 種方法數。
如果不選的話,那麼有 dp[2][2] 種方法數。

j == 0,一共有 1 種方法數,也就是什麼都不選。
i < j,一共有 0 種方法數,因爲不可能選到。

Example image

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int numWays(vector<string>& words, string target) {
        int n = words[0].size();
        int m = target.size();
        int mod = 1e9 + 7;
        vector<vector<int>> freq(n, vector<int> (26, 0));
        for (string word: words){
            for (int i = 0; i < n; i++){
                freq[i][word[i] - 'a']++;
            }
        }
        // memo[i][j] = first i characters in word, ways to form target[j:]
        vector<vector<long long>> memo(n + 1, vector<long long> (m + 1, 0)); 
        function<long long(int, int)> dp = [&](int i, int j) -> long long {
            if (j == 0){
                return 1;
            }
            if (i < j){
                return 0;
            }
            if (memo[i][j] > 0){
                return memo[i][j];
            }
            memo[i][j] = (dp(i - 1, j) + dp(i - 1, j - 1) * freq[i - 1][target[j - 1] - 'a']) % mod;
            return memo[i][j];
        };
        return dp(n, m);
    }

time complexity: O( nk + nm )
space complextiy: O( 26n + nm )
where n = words[0].size(), m = target.size(), k = words.size()

也可以改成 bottom up 的寫法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int numWays(vector<string>& words, string target) {
    int n = words[0].size();
    int m = target.size();
    int mod = 1e9 + 7;
    vector<vector<int>> freq(n, vector<int> (26, 0));
    for (string word: words){
        for (int i = 0; i < n; i++){
            freq[i][word[i] - 'a']++;
        }
    }
    vector<vector<long long>> memo(n + 1, vector<long long> (m + 1, 0));
    for (int i = 0; i <= n; i++){
        for (int j = 0; j <= i && j <= m; j++){
            if (j == 0){
                memo[i][j] = 1;
            }
            else {
                memo[i][j] = (memo[i - 1][j] + memo[i - 1][j - 1] * freq[i - 1][target[j - 1] - 'a']) % mod;
            }
        }
    }
    return memo[n][m];
}

計算 memo[i] 時只有用到 memo[i - 1],可以進一步優化空間複雜度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int numWays(vector<string>& words, string target) {
    int n = words[0].size();
    int m = target.size();
    int mod = 1e9 + 7;
    vector<vector<int>> freq(n, vector<int> (26, 0));
    for (string word: words){
        for (int i = 0; i < n; i++){
            freq[i][word[i] - 'a']++;
        }
    }
    vector<long long> memo(m + 1, 0), prev(m + 1, 0);
    for (int i = 0; i <= n; i++){
        for (int j = 0; j <= i && j <= m; j++){
            if (j == 0){
                memo[j] = 1;
            }
            else {
                memo[j] = (prev[j] + prev[j - 1] * freq[i - 1][target[j - 1] - 'a']) % mod;
            }
        }
        swap(memo, prev);
    }
    return prev[m];
}

space complextiy: O( 26n + m )

實際上空間複雜度可以再優化,前面的 freq 可以不用先算。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int numWays(vector<string>& words, string target) {
    int n = words[0].size();
    int m = target.size();
    int mod = 1e9 + 7;
    
    vector<long long> memo(m + 1, 0), prev(m + 1, 0);
    prev[0] = 1;
    for (int i = 1; i <= n; i++){
        
        vector<int> freq(26, 0);
        
        for (string &word: words){
            freq[word[i - 1] - 'a']++;
        }
        
        for (int j = 0; j <= i && j <= m; j++){
            if (j == 0){
                memo[j] = 1;
            }
            else {
                memo[j] = (prev[j] + prev[j - 1] * freq[target[j - 1] - 'a']) % mod;
            }
        }
        swap(memo, prev);
    }
    return prev[m];
}

space complextiy: O( n + m )