Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
Program to find number of ways to form a target string given a dictionary in Python
Suppose we have a list of strings called words, where all elements are of the same length. We also have a string called target. We have to generate target using the given words under the following rules:
We should generate target from left to right.
To get the ith character (0-indexed) of target, we can select the kth character of the jth string in words when target[i] is same as words[j][k].
Once we use the kth character of the jth string of words, we cannot use the xth character of any string in words where x <= k.
Repeat this process until we form the entire target string.
We have to find the number of ways to get target from words. The answer may be very large, so return answer modulo 10^9 + 7.
Example
If the input is like words = ["pqqp","qppq"], target = "qpq", then the output will be 4 because:
"qpq" ? at index 0 ("qppq"), at index 1 ("qppq"), at index 2 ("pqqp")
"qpq" ? at index 0 ("qppq"), at index 1 ("qppq"), at index 3 ("qppq")
"qpq" ? at index 0 ("qppq"), at index 2 ("qppq"), at index 3 ("qppq")
"qpq" ? at index 1 ("pqqp"), at index 2 ("qppq"), at index 3 ("qppq")
Algorithm Steps
To solve this, we will follow these steps:
m := length of each word (number of columns)
n := length of target string
d := a list of size m, filled with m different empty dictionaries to count character frequencies at each position
For each word w in words, count frequency of each character at each position
Define a recursive function dfs(i, j) where i is target index and j is word position index
Base cases: if i == n return 1 (target formed), if j == m return 0 (no more positions)
Return sum of: skip current position + use current position with available characters
Implementation
from collections import Counter
def solve(words, target):
m, n = len(words[0]), len(target)
# Count frequency of each character at each position
char_count = [Counter() for _ in range(m)]
for word in words:
for j, char in enumerate(word):
char_count[j][char] += 1
def dfs(target_idx, word_pos):
# Base case: target string is completely formed
if target_idx == n:
return 1
# Base case: no more positions available in words
if word_pos == m:
return 0
# Skip current position + use current position with matching characters
ways = dfs(target_idx, word_pos + 1)
if target[target_idx] in char_count[word_pos]:
ways += dfs(target_idx + 1, word_pos + 1) * char_count[word_pos][target[target_idx]]
return ways % (10**9 + 7)
return dfs(0, 0)
# Test the function
words = ["pqqp", "qppq"]
target = "qpq"
result = solve(words, target)
print(f"Number of ways to form '{target}': {result}")
The output of the above code is:
Number of ways to form 'qpq': 4
How It Works
The algorithm uses dynamic programming with memoization. First, it preprocesses the words to count character frequencies at each position. Then it uses recursion to explore all valid ways:
Skip position: Move to next position without using any character
Use position: If target character exists at current position, multiply ways by its frequency
The constraint ensures we only move forward in positions, preventing reuse of earlier positions.
Optimized Version with Memoization
from collections import Counter
def solve_optimized(words, target):
m, n = len(words[0]), len(target)
MOD = 10**9 + 7
# Count character frequencies at each position
char_count = [Counter() for _ in range(m)]
for word in words:
for j, char in enumerate(word):
char_count[j][char] += 1
# Memoization cache
memo = {}
def dfs(target_idx, word_pos):
if (target_idx, word_pos) in memo:
return memo[(target_idx, word_pos)]
if target_idx == n:
return 1
if word_pos == m:
return 0
# Skip current position
ways = dfs(target_idx, word_pos + 1)
# Use current position if character matches
if target[target_idx] in char_count[word_pos]:
ways += dfs(target_idx + 1, word_pos + 1) * char_count[word_pos][target[target_idx]]
ways %= MOD
memo[(target_idx, word_pos)] = ways
return ways
return dfs(0, 0)
# Test with example
words = ["pqqp", "qppq"]
target = "qpq"
print(solve_optimized(words, target))
4
Conclusion
This problem uses dynamic programming to count valid ways of forming a target string from dictionary words. The key insight is preprocessing character frequencies and using recursion with memoization for optimal performance.
