String Pattern Matching Naive in DSA Rust - Time & Space Complexity
We want to understand how long it takes to find a pattern inside a text using a simple method.
How does the time needed grow when the text or pattern gets bigger?
Analyze the time complexity of the following code snippet.
fn naive_pattern_search(text: &str, pattern: &str) -> Vec {
let n = text.len();
let m = pattern.len();
let mut result = Vec::new();
if m > n {
return result;
}
for i in 0..=n - m {
let mut j = 0;
while j < m && text.as_bytes()[i + j] == pattern.as_bytes()[j] {
j += 1;
}
if j == m {
result.push(i);
}
}
result
}
This code checks every possible position in the text to see if the pattern matches starting there.
- Primary operation: The outer loop runs over the text positions, and the inner loop compares characters of the pattern with the text.
- How many times: The outer loop runs about n - m + 1 times, and for each, the inner loop can run up to m times.
As the text and pattern get longer, the number of checks grows roughly by multiplying their sizes.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 (text), 3 (pattern) | About 8 * 3 = 24 checks |
| 100 (text), 5 (pattern) | About 96 * 5 = 480 checks |
| 1000 (text), 10 (pattern) | About 991 * 10 = 9,910 checks |
Pattern observation: The total work grows roughly by multiplying the text length by the pattern length.
Time Complexity: O(n * m)
This means the time needed grows proportionally to the product of the text size and the pattern size.
[X] Wrong: "The search takes only O(n) time because it looks through the text once."
[OK] Correct: The inner loop compares each character of the pattern for every position, so the work multiplies by the pattern length.
Understanding this simple search helps build a foundation for more efficient methods and shows you can analyze nested loops clearly.
"What if we stopped checking as soon as one character didn't match? How would that affect the time complexity in the best case?"
Practice
Solution
Step 1: Understand naive matching approach
The naive algorithm tries to match the pattern starting at every position in the text.Step 2: Compare with other methods
Other methods like hashing or prefix tables are not part of naive matching.Final Answer:
Check every possible position in the text for the pattern -> Option AQuick Check:
Naive = Check all positions [OK]
- Confusing naive with hashing or prefix methods
- Thinking naive skips positions
- Assuming sorting is involved
Solution
Step 1: Determine valid start positions
The pattern can only start at positions where it fits fully in the text, so from 0 to text length minus pattern length.Step 2: Check Rust range syntax
Using 0..=text.len() - pattern.len() includes the last valid index.Final Answer:
for i in 0..=text.len() - pattern.len() { /* check pattern */ } -> Option BQuick Check:
Loop range = 0 to text.len()-pattern.len() [OK]
- Looping beyond valid start positions causing panic
- Using pattern length as loop limit
- Off-by-one errors in range
let text = "abracadabra";
let pattern = "abra";
let mut positions = Vec::new();
for i in 0..=text.len() - pattern.len() {
if &text[i..i+pattern.len()] == pattern {
positions.push(i);
}
}
println!("{:?}", positions);Solution
Step 1: Identify pattern matches in text
The pattern "abra" appears at index 0 ("abra") and index 7 ("abra") in "abracadabra".Step 2: Confirm positions collected
Positions vector collects indices 0 and 7 where pattern matches exactly.Final Answer:
[0, 7] -> Option DQuick Check:
Matches at 0 and 7 only [OK]
- Including partial matches like at index 3
- Off-by-one errors in slicing
- Missing second occurrence
let text = "hello";
let pattern = "ll";
for i in 0..text.len() - pattern.len() {
if &text[i..i+pattern.len()] == pattern {
println!("Found at {}", i);
}
}Solution
Step 1: Check loop range correctness
The loop uses 0..text.len() - pattern.len(), which excludes the last valid start index.Step 2: Fix range to include last index
Using 0..=text.len() - pattern.len() includes the last position where pattern can start.Final Answer:
Range should be 0..=text.len() - pattern.len() to include last position -> Option AQuick Check:
Use inclusive range for last valid index [OK]
- Using exclusive range causing missed matches
- Incorrect slicing indices
- Unnecessary conversion to chars
Example: text = "aaaa", pattern = "aa" should count 3 occurrences.
Solution
Step 1: Understand overlapping matches
Overlapping means after finding a match, next check starts just one position ahead, not skipping full pattern length.Step 2: Adjust loop increment
Increment index by 1 each time to allow overlapping matches to be found.Final Answer:
Increment index by 1 after each check instead of pattern length -> Option CQuick Check:
Step by 1 to count overlaps [OK]
- Skipping by pattern length missing overlaps
- Using hash map unnecessarily
- Reversing strings does not help
Ready to prove it?
Pass the test and this pattern moves unknown
