Number of Strings That Appear as Substrings in Word
EasyPrompt
Given an array of strings patterns and a string word, return the number of strings in patterns that exist as a substring in word.
A substring is a contiguous sequence of characters within a string.
Example 1:
Input: patterns = ["a","abc","bc","d"], word = "abc"
Output: 3
Explanation:
- "a" appears as a substring in "abc".
- "abc" appears as a substring in "abc".
- "bc" appears as a substring in "abc".
- "d" does not appear as a substring in "abc".
3 of the strings in patterns appear as a substring in word.Example 2:
Input: patterns = ["a","b","c"], word = "aaaaabbbbb"
Output: 2
Explanation:
- "a" appears as a substring in "aaaaabbbbb".
- "b" appears as a substring in "aaaaabbbbb".
- "c" does not appear as a substring in "aaaaabbbbb".
2 of the strings in patterns appear as a substring in word.Example 3:
Input: patterns = ["a","a","a"], word = "ab"
Output: 3
Explanation: Each of the patterns appears as a substring in word "ab".
Constraints:
1 <= patterns.length <= 1001 <= patterns[i].length <= 1001 <= word.length <= 100patterns[i]andwordconsist of lowercase English letters.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves manually implementing a substring search algorithm. We iterate through each pattern and then, for each pattern, we iterate through all possible starting positions in the word to see if a match can be found. This is the most fundamental way to solve the problem but also the least efficient.
Algorithm
- Initialize a counter
countto zero. - Loop through each
patternstring in thepatternsarray. - For each
pattern, start a nested loop to check for its presence inword. - This inner check involves another loop that iterates from the first character of
wordup to the last possible starting point forpattern(i.e.,word.length() - pattern.length()). - At each starting position
iinword, we compare the substring ofwordof lengthpattern.length()starting atiwith thepatterncharacter by character. - If a full match is found for the current
pattern, we incrementcountand break the inner loops to move to the next pattern in thepatternsarray. - After checking all patterns, the final
countis returned.
Walkthrough
The core idea is to simulate the process of finding a substring without using any library helpers. We take each pattern from the input array and try to find it within the word. To do this, we slide the pattern over the word one character at a time. For each possible starting position in word, we compare the corresponding segment of word with the pattern. If all characters match, we've found a substring. We then increment our counter and move on to the next pattern to avoid overcounting for a single pattern that might appear multiple times.
class Solution { public int numOfStrings(String[] patterns, String word) { int count = 0; for (String pattern : patterns) { if (isSubstring(word, pattern)) { count++; } } return count; } private boolean isSubstring(String text, String pattern) { int n = text.length(); int m = pattern.length(); if (m == 0) return true; if (n < m) return false; for (int i = 0; i <= n - m; i++) { int j; for (j = 0; j < m; j++) { if (text.charAt(i + j) != pattern.charAt(j)) { break; } } if (j == m) { return true; // Found the pattern } } return false; // Pattern not found }}Complexity
Time
O(N * W * M), where `N` is the number of patterns, `W` is the length of `word`, and `M` is the maximum length of a pattern. For each of the `N` patterns, we scan through `W` possible starting positions in `word`, and for each position, we compare up to `M` characters.
Space
O(1), as we only use a few variables to keep track of the count and loop indices, not dependent on the input size.
Trade-offs
Pros
Simple to understand the logic from first principles.
Doesn't rely on built-in library functions for the core substring search logic.
Cons
Inefficient due to the triple nested loop structure, leading to a high time complexity.
Re-implements functionality that is already available and highly optimized in standard libraries.
More verbose and error-prone than using built-in methods.
This is the most straightforward and practical approach for this problem. We can leverage the built-in String.contains() method which is highly optimized for substring searching. This method abstracts away the complexity of the search algorithm, leading to clean and efficient code.
Algorithm
- Initialize a counter
count = 0. - For each
string patternin thepatternsarray: - Check if
word.contains(pattern)is true. - If it is, increment
count. - After iterating through all patterns, return
count.
Walkthrough
The algorithm is very simple. We initialize a counter variable to zero. We then iterate through the patterns array one by one. For each pattern string, we call word.contains(pattern). This method returns true if pattern is a substring of word, and false otherwise. If the method returns true, we increment our counter. After the loop finishes, the counter will hold the total number of patterns that are substrings of word, and we return this value. This is the intended and most common solution for such a problem in a practical or contest setting.
class Solution { public int numOfStrings(String[] patterns, String word) { int count = 0; for (String pattern : patterns) { if (word.contains(pattern)) { count++; } } return count; }}Complexity
Time
O(N * W * M), where `N` is the number of patterns, `W` is the length of `word`, and `M` is the average length of a pattern. The `contains` method has a time complexity of roughly `O(W * M)` in the worst case. However, the underlying implementation is often much faster in practice than a manual brute-force search due to optimizations (like KMP or Boyer-Moore algorithms).
Space
O(1), as no extra space proportional to the input size is used.
Trade-offs
Pros
Extremely simple and concise code.
Leverages optimized, built-in library functions, making it very efficient in practice for the given constraints.
Highly readable and less prone to implementation errors.
Cons
The exact performance depends on the language's specific implementation of the
containsmethod, though it's generally very good.
For problems involving many substring queries on a single text, a more advanced approach is to use a specialized data structure like a Suffix Tree or a Suffix Automaton. This involves a pre-processing step to build the structure for the word, after which each pattern query can be answered very quickly. This approach is asymptotically the most efficient.
Algorithm
- Preprocessing: Build a Suffix Automaton or Suffix Tree for
word. This takesO(W)time. - Querying: Initialize
count = 0. - For each
patterninpatterns: - Check if
patternexists in the pre-built data structure by traversing it. This takesO(M_i)time, whereM_iis the length of the current pattern. - If it exists, increment
count. - Return
count.
Walkthrough
This method separates the problem into two phases: preprocessing and querying. First, we process the word by building a data structure that indexes all of its substrings. A Suffix Automaton is a perfect fit, as it can be built in O(W) time and can check for the existence of any substring of length M in O(M) time. After the automaton for word is built, we simply iterate through each pattern, check for its presence in the automaton, and increment a counter if it's found. While this is the fastest approach in terms of time complexity, its implementation complexity is very high and not practical for this problem's constraints.
// Conceptual Code - Suffix Automaton implementation is non-trivial// and not expected for this problem. The built-in `contains` method// is the practical and sufficient solution.class Solution { public int numOfStrings(String[] patterns, String word) { // In a real-world scenario with massive scale, one would: // 1. Pre-process 'word' into a Suffix Tree/Automaton. // SuffixAutomaton sa = new SuffixAutomaton(word); // 2. Query for each pattern. int count = 0; for (String pattern : patterns) { // if (sa.isSubstring(pattern)) { // count++; // } // Given the constraints, this is the best approach: if (word.contains(pattern)) { count++; } } return count; }}Complexity
Time
O(W + S), where `W` is the length of `word` and `S` is the sum of the lengths of all strings in `patterns`. Building the automaton takes `O(W)`. Each query takes `O(M_i)` where `M_i` is the length of the pattern, so the total query time is `O(S)`.
Space
O(W), as the Suffix Automaton or Suffix Tree requires space proportional to the length of the `word` to store its nodes and transitions.
Trade-offs
Pros
Asymptotically the most efficient approach, especially when the
wordis very long and there are many patterns to check.Excellent performance for substring search after the initial preprocessing cost.
Cons
Very complex to implement correctly from scratch.
The overhead of building the data structure might make it slower than the simpler
contains()method for small inputs like those specified in the problem constraints.It is considered overkill for this particular problem.
Solutions
Solution
class Solution {public int numOfStrings(String[] patterns, String word) { int ans = 0; for (String p : patterns) { if (word.contains(p)) { ++ans; } } return ans; }}class Solution {public: int numOfStrings(vector<string> &patterns, string word) { int ans = 0; for (auto &p : patterns) { ans += word.find(p) != string ::npos; } return ans; }};class Solution: def numOfStrings( self, patterns: List[str], word: str) -> int: return sum(p in word for p in patterns)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.