Problem
Given a text string (haystack) and a pattern string (needle), return the index of the first position where needle appears inside haystack, or -1 if it never does.
- Input: haystack =
"sadbutsad", needle ="sad" - Output:
0 - Explanation: "sad" matches starting at index 0 (it also appears at index 6, but we want the first).
Counter-example: haystack = "leetcode", needle = "leeto" โ -1, because no contiguous slice of "leetcode" matches "leeto" exactly.
Intuition
We need to find where a fixed-length pattern first appears inside a longer text. The straightforward approach is to slide the pattern across the text one position at a time and compare. KMP improves this by precomputing a "failure function" for the pattern โ when a mismatch occurs, instead of restarting from scratch, we jump backward in the pattern (never in the text), reusing characters we already verified.
Approach 1 โ Sliding Window Comparison
For each valid starting index in haystack, extract a window of the same length as needle and compare directly.
- Compute lengths:
n = len(haystack),m = len(needle). - Iterate
startfrom0ton - minclusive โ these are all valid starting positions. - Extract
haystack[start : start + m]and compare it toneedle. - If they match, return
start. - If no match was found after the loop, return
-1.
1def strStr(haystack: str, needle: str) -> int:
2 haystack_len = len(haystack)
3 needle_len = len(needle)
4
5 for start in range(haystack_len - needle_len + 1): # +1 so last valid window is included
6 if haystack[start:start + needle_len] == needle:
7 return start
8
9 return -1Time: O(n * m) โ each of the n starting positions can require up to m character comparisons in the worst case.
Space: O(m) โ extracting a window allocates a temporary string of length m.
Approach 2 โ KMP (Knuth-Morris-Pratt)
Build a failure function (LPS array) for needle, then scan haystack with two independent pointers โ the haystack pointer never moves backward.
- Allocate
lps[0..m-1]wherelps[i]= length of the longest proper prefix ofneedle[0..i]that is also a suffix. - Fill
lps: track aprefix_lenpointer; whenneedle[i] == needle[prefix_len]extend the match; on mismatch fall back vialps[prefix_len - 1]without advancingi. - Scan: maintain haystack index
iand needle indexj. - When
haystack[i] == needle[j], advance both. - When
j == needle_len, a full match ends atiโ returni - j. - On mismatch: if
j > 0, setj = lps[j - 1]to reuse the longest valid prefix; otherwise incrementito move past the failed position.
1def strStr(haystack: str, needle: str) -> int:
2 needle_len = len(needle)
3 haystack_len = len(haystack)
4 if needle_len == 0: # guard: empty needle matches at position 0
5 return 0
6
7 # lps[i] = longest proper prefix of needle[0..i] that is also a suffix
8 lps = [0] * needle_len
9 prefix_len = 0
10 i = 1
11 while i < needle_len:
12 if needle[i] == needle[prefix_len]:
13 prefix_len += 1
14 lps[i] = prefix_len
15 i += 1
16 elif prefix_len > 0:
17 prefix_len = lps[prefix_len - 1] # fall back without moving i
18 else:
19 lps[i] = 0
20 i += 1
21
22 i = 0 # haystack pointer
23 j = 0 # needle pointer
24 while i < haystack_len:
25 if haystack[i] == needle[j]:
26 i += 1
27 j += 1
28 if j == needle_len:
29 return i - j # match ends at i, started at i - j
30 elif i < haystack_len and haystack[i] != needle[j]:
31 if j != 0:
32 j = lps[j - 1] # reuse matched prefix instead of resetting to 0
33 else:
34 i += 1 # no prefix to reuse; must advance past this character
35
36 return -1Time: O(n + m) โ O(m) to build the LPS array, then O(n) to scan haystack because i never moves backward.
Space: O(m) โ the LPS array holds one entry per character in needle.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Sliding Window | O(n * m) | O(m) | Short patterns or one-off scripts where simplicity matters more than speed |
| KMP | O(n + m) | O(m) | Large inputs or patterns with repeated structure like "aaab" where brute force degrades badly |
Common Mistakes
- Off-by-one in the brute-force loop:
range(haystack_len - needle_len)stops one position too early โ the last valid starting index ishaystack_len - needle_lenitself, so the correct upper bound is+ 1. - Unsigned underflow in C++:
haystack.size() - needle.size()usessize_t(unsigned), so if needle is longer the result wraps to a huge number and the loop runs out of bounds. Cast both lengths tointbefore subtracting. - KMP infinite loop when j = 0: When there's a mismatch and
j == 0, there is no prefix to fall back to โ you must incrementi. Omitting theelse: i += 1branch causes the loop to spin on the same character forever. - Wrong LPS index on backtrack: On a mismatch at position
j, the fallback islps[j - 1](the prefix length for the previous matched position), notlps[j]. Usinglps[j]reads one slot too far and corrupts the search. - Crashing on empty needle: If
needle_len == 0, the search loop immediately accessesneedle[0](orneedle.charAt(0)), causing an index-out-of-bounds error. Add an early return before entering the search.
Related Problems
Palindromic Substringsโ systematically examines every substring position, similar to the sliding window approach hereFind All Anagrams in a Stringโ fixed-width window scan across a text, same structural pattern as brute-force approachMinimum Window Substringโ variable-width window search with a character-frequency constraintLongest Common Prefixโ character-by-character comparison across strings, same mechanics as KMP's matching phaseValid Anagramโ foundational string equivalence check that underlies many pattern-matching problems