Number of Ways to Form a Target String Given a Dictionary
Hard · 1-D Dynamic Programming · Explanation and code example
Dynamic Programming (Space Optimized)
How it works
Since we only need the previous row of the DP table to compute the current row, we can reduce space by using a single 1D array.
Steps
- Precompute the character frequency table.
- Use a 1D DP array of size
m + 1. - Iterate from
i = ndown to0, and for each row, iterate through columns in reverse. - Maintain the value from the next row using a temporary variable.
- Return
dp[0]modulo10^9 + 7.
Complexity
- Time complexity:
O(m * (n + N)) - Space complexity:
O(m)
Where
Nis the number of words,mis the length of each word, andnis the length of thetargetstring.
Things to watch for
Forgetting to Precompute Character Frequencies
A common mistake is iterating through all words at each step to count matching characters. This leads to TLE because the time complexity becomes O(N) per state transition. Instead, precompute a frequency table cnt[k][c] storing how many words have character c at column k before starting the DP.
Integer Overflow When Multiplying Count by DP Value
When computing cnt[k][c] * dp[i+1][k+1], both values can be large. In languages like Java and C++, multiplying two int values can overflow before the modulo operation is applied. Always cast to long or long long before multiplication: (long) cnt[k][c] * dp[i+1][k+1] % MOD.
Using Wrong Base Case for Bottom-Up DP
In bottom-up DP, the base case placement matters. Setting dp[n][k] = 1 for all k is incorrect because only dp[n][m] = 1 represents a valid completion (target fully matched at the end). The recurrence dp[i][k] = dp[i][k+1] propagates this value correctly, but initializing all dp[n][k] = 1 would overcount.
Confusing Column Index with Target Index
The DP state involves two indices: i for the target position and k for the column in words. A common error is swapping their roles or forgetting that k can skip ahead (we can skip columns) while i must advance exactly by 1 each time we pick a character. The column index k must always be at least as large as the target index i.
Not Handling Target Longer Than Word Length
If the target string is longer than the word length (n > m), it is impossible to form the target since we need at least n columns. The algorithm should return 0 in this case. While the recursion handles this naturally (running out of columns before finishing target), explicitly checking can prevent unnecessary computation.
Python code example
from typing import List
class Solution:
def numWays(self, words: List[str], target: str) -> int:
MOD = 10**9 + 7
n, m = len(target), len(words[0])
cnt = [[0] * 26 for _ in range(m)]
for word in words:
for i, c in enumerate(word):
cnt[i][ord(c) - ord('a')] += 1
dp = [0] * (m + 1)
dp[m] = 1
for i in range(n, -1, -1):
nxt = 1 if i == n - 1 else 0
for k in range(m - 1, -1, -1):
cur = dp[k]
dp[k] = dp[k + 1]
if i < n:
c = ord(target[i]) - ord('a')
dp[k] = (dp[k] + cnt[k][c] * nxt) % MOD
nxt = cur
dp[m] = 0
return dp[0]
C++ code example
class Solution {
private:
static const int MOD = 1e9 + 7;
public:
int numWays(vector<string>& words, string target) {
int n = target.size(), m = words[0].size();
vector<vector<int>> cnt(m, vector<int>(26, 0));
for (const string& word : words) {
for (int i = 0; i < word.size(); i++) {
cnt[i][word[i] - 'a']++;
}
}
vector<int> dp(m + 1);
dp[m] = 1;
for (int i = n; i >= 0; i--) {
int nxt = i == n - 1 ? 1 : 0;
for (int k = m - 1; k >= 0; k--) {
int cur = dp[k];
dp[k] = dp[k + 1];
if (i < n) {
int c = target[i] - 'a';
dp[k] = (dp[k] + (long long) cnt[k][c] * nxt) % MOD;
}
nxt = cur;
}
dp[m] = 0;
}
return dp[0];
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.