Given the strings s1 and s2 of size n and the string evil, return the number of good strings.
A good string has size n, it is alphabetically greater than or equal to s1, it is alphabetically smaller than or equal to s2, and it does not contain the string evil as a substring. Since the answer can be a huge number, return this modulo 109 + 7.
Example 1:
Input: n = 2, s1 = "aa", s2 = "da", evil = "b" Output: 51 Explanation: There are 25 good strings starting with 'a': "aa","ac","ad",...,"az". Then there are 25 good strings starting with 'c': "ca","cc","cd",...,"cz" and finally there is one good string starting with 'd': "da".
Example 2:
Input: n = 8, s1 = "leetcode", s2 = "leetgoes", evil = "leet" Output: 0 Explanation: All strings greater than or equal to s1 and smaller than or equal to s2 start with the prefix "leet", therefore, there is not any good string.
Example 3:
Input: n = 2, s1 = "gx", s2 = "gz", evil = "x" Output: 2
Constraints:
s1.length == ns2.length == ns1 <= s21 <= n <= 5001 <= evil.length <= 50Problem Overview: You are given two strings s1 and s2 of length n, and a forbidden substring evil. The task is to count how many strings of length n lie lexicographically between s1 and s2 (inclusive) and do not contain evil as a substring.
Approach 1: Dynamic Programming with Character Level Constraints (O(n * m * 26) time, O(n * m * 4) space)
This approach builds the string character by character while maintaining constraints from s1 and s2. A DP state typically tracks index, how many characters of evil have matched so far, and two flags indicating whether the current prefix is still bounded by s1 or s2. For each position, iterate through possible characters ('a' to 'z') within the allowed bounds and update the match length for evil. If the match length reaches len(evil), that branch is discarded. This method combines dynamic programming with prefix checking to prune invalid strings early.
Approach 2: Dynamic Programming with Automata (O(n * m * 26) time, O(m * 26) automaton space)
The optimal strategy builds a finite automaton for the evil string using prefix transitions. Each state represents how many characters of evil are currently matched. When you append a new character, the automaton immediately tells the next state using precomputed transitions. The DP state becomes (index, matchedLen, lowerBound, upperBound). At every step, iterate over valid characters and transition using the automaton. This avoids recomputing prefix matches repeatedly and keeps the DP transitions fast. It heavily relies on string matching concepts combined with dynamic programming.
Approach 3: String Matching with KMP for Evil Substring (O(m) preprocessing, integrated DP O(n * m * 26))
The Knuth-Morris-Pratt (KMP) algorithm is used to preprocess the evil substring by building the LPS (longest prefix suffix) array. This array determines how the match length changes when characters mismatch while constructing candidate strings. During DP transitions, instead of scanning prefixes manually, you update the matched prefix length using the LPS table. This ensures substring detection happens in constant time per transition while avoiding full substring comparisons.
Recommended for interviews: Interviewers usually expect the DP + KMP automaton approach. It shows you understand how to combine lexicographic constraints, stateful substring matching, and memoized DP. Starting with the character-constrained DP helps demonstrate the core idea. Converting the substring check into a KMP-based automaton is the step that turns a slow solution into the optimal one.
This approach involves the use of dynamic programming combined with an automaton to identify occurrences of the 'evil' substring efficiently. The fundamental idea is to construct an automaton that helps in checking the presence of the evil substring while generating potential 'good' strings between s1 and s2.
The strategy is to use a 3D DP array: dp[pos][evil_length][bounded], where:
pos denotes the current position in the string being considered.evil_length is the length of the substring in the automaton matching the of the evil string.bounded is a flag indicating whether the current string prefix is tightly bounded by s1 and s2.The automaton is utilized to efficiently transition between states based on the current character appended, reducing the time complexity associated with substring checking.
The above Python solution uses a KMP-based automaton to efficiently process and transition states to check for occurrences of the evil substring within potential good strings. The state transitions are precomputed in the KMPAutomaton class, allowing for quick look-up and decision making during the DP recursion. The DP function dfs manages the recursive exploration of possible good strings, accounting for boundaries implied by s1 and s2, and ensuring that the evil substring does not appear.
Time Complexity: O(n * |evil| * 26) due to processing each position with every character and state transition.
Space Complexity: O(n * |evil| * 2) for the memoization table of the DP states.
We can use dynamic programming to solve this problem by considering three conditions for each position in the string: whether it should be bounded by s1, s2, and whether it currently contains the 'evil' substring. We can define a dp[pos][is_bound_s1][is_bound_s2][evil_len] table that keeps track of the number of good strings of length n that start from pos, have or not reached the boundaries s1 and s2 constraints, and the number of characters currently matching with the leading characters of the 'evil' string.
This C code utilizes dynamic programming and recursion to calculate the number of 'good' strings. We create a 4D dp array to store probabilities of acquiring 'good' strings up to a particular index with respect to s1 and s2 boundaries and the match length with 'evil'. The logic iteratively checks each character between given bounds and determines the next state recursively. The final result is computed efficiently using memoization.
Time Complexity: O(n * 2 * 2 * m) where n is length of s1/s2, m is length of 'evil'
Space Complexity: O(n * 2 * 2 * m)
We can optimize the solution by employing the KMP algorithm to check for the 'evil' substring. While iterating through potential strings, instead of directly looking for substrings, use the KMP partial match table to efficiently find matching 'evil' patterns. This approach also integrates with dynamic programming to manage state transitions.
This version of the C solution enhances the search operation by incorporating a KMP-based algorithm to ascertain the presence of 'evil'. It builds an LPS (Longest Prefix Suffix) array for 'evil' to guide the substring search while iterating character choices. This enables efficient matching in dynamically generating strings.
Time Complexity: O(n * 2 * 2 * m), Space Complexity: O(n * 2 * 2 * m)
| Approach | Complexity |
|---|---|
| Dynamic Programming with Automata | Time Complexity: O(n * |evil| * 26) due to processing each position with every character and state transition. Space Complexity: O(n * |evil| * 2) for the memoization table of the DP states. |
| Dynamic Programming with Character Level Constraints | Time Complexity: O(n * 2 * 2 * m) where n is length of s1/s2, m is length of 'evil' |
| String Matching with KMP for 'Evil' Substring | Time Complexity: O(n * 2 * 2 * m), Space Complexity: O(n * 2 * 2 * m) |
| Approach | Time | Space | When to Use |
|---|---|---|---|
| DP with Character Level Constraints | O(n * m * 26) | O(n * m * 4) | Good starting point to understand lexicographic bounds and DP state transitions. |
| DP with Automata | O(n * m * 26) | O(m * 26) | Optimal approach when substring detection must be fast during DP transitions. |
| KMP-based Evil Substring Matching | O(m) preprocessing + O(n * m * 26) | O(m) | Best when integrating efficient substring matching into DP using prefix function. |
LeetCode 1397. Find All Good Strings • Happy Coding • 2,626 views views
Watch 4 more video solutions →Practice Find All Good Strings with our built-in code editor and test cases.
Practice on FleetCodePractice this problem
Open in Editor