You are given two strings needle and haystack, return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
String Manipulation - Basic operations like substring comparison and character indexing
KMP Algorithm (Optional) - Understanding the Longest Proper Prefix Suffix (LPS) array for efficient pattern matching
Rolling Hash / Rabin-Karp (Optional) - Using hash functions to compare substrings in O(1) time
1. Brute Force
Intuition
The simplest way to find a substring is to try every possible starting position in the haystack. At each position, we compare characters one by one with the needle. If all characters match, we found our answer. If any character doesn't match, we move to the next starting position and try again.
Algorithm
Iterate through each valid starting position i in the haystack (from 0 to n - m, where n is the haystack length and m is the needle length).
For each starting position, compare characters of the haystack starting at i with characters of the needle.
If all characters match (we reach the end of the needle), return the starting position i.
If any character doesn't match, break out of the inner loop and try the next starting position.
If no match is found after checking all positions, return -1.
classSolution:defstrStr(self, haystack:str, needle:str)->int:
n, m =len(haystack),len(needle)for i inrange(n - m +1):
j =0while j < m:if haystack[i + j]!= needle[j]:break
j +=1if j == m:return i
return-1
publicclassSolution{publicintstrStr(String haystack,String needle){int n = haystack.length(), m = needle.length();for(int i =0; i < n - m +1; i++){int j =0;while(j < m){if(haystack.charAt(i + j)!= needle.charAt(j)){break;}
j++;}if(j == m)return i;}return-1;}}
classSolution{public:intstrStr(string haystack, string needle){int n = haystack.length(), m = needle.length();for(int i =0; i < n - m +1; i++){int j =0;while(j < m){if(haystack[i + j]!= needle[j]){break;}
j++;}if(j == m)return i;}return-1;}};
classSolution{/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/strStr(haystack, needle){let n = haystack.length,
m = needle.length;for(let i =0; i < n - m +1; i++){let j =0;while(j < m){if(haystack[i + j]!== needle[j]){break;}
j++;}if(j === m)return i;}return-1;}}
publicclassSolution{publicintStrStr(string haystack,string needle){int n = haystack.Length, m = needle.Length;for(int i =0; i <= n - m; i++){int j =0;while(j < m){if(haystack[i + j]!= needle[j]){break;}
j++;}if(j == m){return i;}}return-1;}}
funcstrStr(haystack string, needle string)int{
n, m :=len(haystack),len(needle)for i :=0; i <= n-m; i++{
j :=0for j < m {if haystack[i+j]!= needle[j]{break}
j++}if j == m {return i
}}return-1}
class Solution {funstrStr(haystack: String, needle: String): Int {val n = haystack.length
val m = needle.length
for(i in0..n - m){var j =0while(j < m){if(haystack[i + j]!= needle[j]){break}
j++}if(j == m){return i
}}return-1}}
classSolution{funcstrStr(_ haystack:String,_ needle:String)->Int{let n = haystack.count
let m = needle.count
if m ==0{return0}if m > n {return-1}let haystackArr =Array(haystack)let needleArr =Array(needle)for i in0...(n - m){var j =0while j < m {if haystackArr[i + j]!= needleArr[j]{break}
j +=1}if j == m {return i
}}return-1}}
Time & Space Complexity
Time complexity: O(n∗m)
Space complexity: O(1)
Where n is the length of the string heystack and m is the length of the string needle.
2. Knuth-Morris-Pratt (KMP) Algorithm
Intuition
The brute force approach wastes work by restarting from scratch after each mismatch. KMP improves this by preprocessing the needle to build a "longest proper prefix which is also suffix" (LPS) array. When a mismatch occurs, the LPS array tells us how many characters we can skip, leveraging the pattern structure to avoid redundant comparisons.
Algorithm
Build the LPS array for the needle:
Initialize an array of the same length as the needle, filled with zeros.
Use two pointers: prevLPS tracks the length of the current longest prefix-suffix, and i scans through the needle.
If characters match, increment both pointers and store the value. If they don't match and prevLPS > 0, fall back to the previous LPS value.
Search for the needle in the haystack:
Use pointer i for the haystack and j for the needle.
If characters match, advance both pointers.
If they don't match and j > 0, use the LPS array to determine where to continue matching in the needle.
If j reaches the needle length, we found a match; return i - m.
funcstrStr(haystack string, needle string)int{if needle ==""{return0}
m :=len(needle)
lps :=make([]int, m)
prevLPS, i :=0,1for i < m {if needle[i]== needle[prevLPS]{
lps[i]= prevLPS +1
prevLPS++
i++}elseif prevLPS ==0{
lps[i]=0
i++}else{
prevLPS = lps[prevLPS-1]}}
i =0
j :=0for i <len(haystack){if haystack[i]== needle[j]{
i++
j++}else{if j ==0{
i++}else{
j = lps[j-1]}}if j == m {return i - m
}}return-1}
class Solution {funstrStr(haystack: String, needle: String): Int {if(needle.isEmpty())return0val m = needle.length
val lps =IntArray(m)var prevLPS =0var i =1while(i < m){if(needle[i]== needle[prevLPS]){
lps[i]= prevLPS +1
prevLPS++
i++}elseif(prevLPS ==0){
lps[i]=0
i++}else{
prevLPS = lps[prevLPS -1]}}
i =0var j =0while(i < haystack.length){if(haystack[i]== needle[j]){
i++
j++}else{if(j ==0){
i++}else{
j = lps[j -1]}}if(j == m){return i - m
}}return-1}}
classSolution{funcstrStr(_ haystack:String,_ needle:String)->Int{if needle.isEmpty {return0}let haystackArr =Array(haystack)let needleArr =Array(needle)let n = haystackArr.count
let m = needleArr.count
var lps =[Int](repeating:0, count: m)var prevLPS =0var i =1while i < m {if needleArr[i]== needleArr[prevLPS]{
lps[i]= prevLPS +1
prevLPS +=1
i +=1}elseif prevLPS ==0{
lps[i]=0
i +=1}else{
prevLPS = lps[prevLPS -1]}}
i =0var j =0while i < n {if haystackArr[i]== needleArr[j]{
i +=1
j +=1}else{if j ==0{
i +=1}else{
j = lps[j -1]}}if j == m {return i - m
}}return-1}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(m)
Where n is the length of the string heystack and m is the length of the string needle.
3. Z-Algorithm
Intuition
The Z-algorithm computes, for each position in a string, the length of the longest substring starting from that position that matches a prefix of the string. By concatenating the needle, a separator character, and the haystack, any position where the Z-value equals the needle length indicates a match. The algorithm uses a "Z-box" to track previously computed matches and skip redundant comparisons.
Algorithm
Concatenate the needle, a special separator (like $), and the haystack into a single string.
Compute the Z-array for this combined string:
Maintain a Z-box defined by [l, r] representing the rightmost substring that matches a prefix.
For each position i, use the Z-box to initialize z[i] when possible.
Extend z[i] by comparing characters directly.
Update the Z-box if the current match extends beyond the previous bounds.
Scan the Z-array starting after the needle and separator. If z[i] equals the needle length, return the corresponding position in the haystack.
classSolution:defstrStr(self, haystack:str, needle:str)->int:ifnot needle:return0
s = needle +"$"+ haystack
n =len(s)
z =[0]* n
l, r =0,0for i inrange(1, n):if i <= r:
z[i]=min(r - i +1, z[i - l])while i + z[i]< n and s[z[i]]== s[i + z[i]]:
z[i]+=1if i + z[i]-1> r:
l, r = i, i + z[i]-1for i inrange(len(needle)+1, n):if z[i]==len(needle):return i -len(needle)-1return-1
publicclassSolution{publicintstrStr(String haystack,String needle){if(needle.isEmpty())return0;String s = needle +"$"+ haystack;int n = s.length();int[] z =newint[n];int l =0, r =0;for(int i =1; i < n; i++){if(i <= r){
z[i]=Math.min(r - i +1, z[i - l]);}while(i + z[i]< n && s.charAt(z[i])== s.charAt(i + z[i])){
z[i]++;}if(i + z[i]-1> r){
l = i;
r = i + z[i]-1;}}for(int i = needle.length()+1; i < n; i++){if(z[i]== needle.length()){return i - needle.length()-1;}}return-1;}}
classSolution{public:intstrStr(string haystack, string needle){if(needle.empty())return0;
string s = needle +"$"+ haystack;int n = s.size();
vector<int>z(n,0);int l =0, r =0;for(int i =1; i < n; i++){if(i <= r){
z[i]=min(r - i +1, z[i - l]);}while(i + z[i]< n && s[z[i]]== s[i + z[i]]){
z[i]++;}if(i + z[i]-1> r){
l = i;
r = i + z[i]-1;}}for(int i = needle.size()+1; i < n; i++){if(z[i]== needle.size()){return i - needle.size()-1;}}return-1;}};
classSolution{/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/strStr(haystack, needle){if(needle ==='')return0;const s = needle +'$'+ haystack;const n = s.length;const z =newArray(n).fill(0);let l =0,
r =0;for(let i =1; i < n; i++){if(i <= r){
z[i]= Math.min(r - i +1, z[i - l]);}while(i + z[i]< n && s[z[i]]=== s[i + z[i]]){
z[i]++;}if(i + z[i]-1> r){
l = i;
r = i + z[i]-1;}}for(let i = needle.length +1; i < n; i++){if(z[i]=== needle.length){return i - needle.length -1;}}return-1;}}
publicclassSolution{publicintStrStr(string haystack,string needle){if(string.IsNullOrEmpty(needle))return0;string s = needle +"$"+ haystack;int n = s.Length;int[] z =newint[n];int l =0, r =0;for(int i =1; i < n; i++){if(i <= r){
z[i]= Math.Min(r - i +1, z[i - l]);}while(i + z[i]< n && s[z[i]]== s[i + z[i]]){
z[i]++;}if(i + z[i]-1> r){
l = i;
r = i + z[i]-1;}}int m = needle.Length;for(int i = m +1; i < n; i++){if(z[i]== m){return i - m -1;}}return-1;}}
funcstrStr(haystack string, needle string)int{if needle ==""{return0}
s := needle +"$"+ haystack
n :=len(s)
z :=make([]int, n)
l, r :=0,0for i :=1; i < n; i++{if i <= r {
z[i]=min(r-i+1, z[i-l])}for i+z[i]< n && s[z[i]]== s[i+z[i]]{
z[i]++}if i+z[i]-1> r {
l, r = i, i+z[i]-1}}
m :=len(needle)for i := m +1; i < n; i++{if z[i]== m {return i - m -1}}return-1}funcmin(a, b int)int{if a < b {return a
}return b
}
class Solution {funstrStr(haystack: String, needle: String): Int {if(needle.isEmpty())return0val s = needle +"$"+ haystack
val n = s.length
val z =IntArray(n)var l =0var r =0for(i in1 until n){if(i <= r){
z[i]=minOf(r - i +1, z[i - l])}while(i + z[i]< n && s[z[i]]== s[i + z[i]]){
z[i]++}if(i + z[i]-1> r){
l = i
r = i + z[i]-1}}val m = needle.length
for(i in m +1 until n){if(z[i]== m){return i - m -1}}return-1}}
classSolution{funcstrStr(_ haystack:String,_ needle:String)->Int{if needle.isEmpty {return0}let s =Array(needle +"$"+ haystack)let n = s.count
var z =[Int](repeating:0, count: n)var l =0var r =0for i in1..<n {if i <= r {
z[i]=min(r - i +1, z[i - l])}while i + z[i]< n && s[z[i]]== s[i + z[i]]{
z[i]+=1}if i + z[i]-1> r {
l = i
r = i + z[i]-1}}let m = needle.count
for i in(m +1)..<n {if z[i]== m {return i - m -1}}return-1}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(n+m)
Where n is the length of the string heystack and m is the length of the string needle.
4. Rabin-Karp Algorithm (Rolling Hash)
Intuition
Instead of comparing characters one by one, we can compare hash values of substrings. If we compute the hash of the needle and the hash of each window in the haystack, matching hashes suggest a potential match. The key insight is using a rolling hash: when sliding the window by one position, we can update the hash in O(1) time by removing the contribution of the outgoing character and adding the incoming one. Using two different hash functions reduces false positives.
Algorithm
Compute the hash of the needle using two different hash functions (different bases and moduli).
Compute the hash of the first window in the haystack (same length as the needle).
Precompute the power values needed to remove the leftmost character's contribution.
Slide the window through the haystack:
If both hashes match the needle's hashes, return the current position.
Update the rolling hash by removing the leftmost character and adding the new rightmost character.
Where n is the length of the string heystack and m is the length of the string needle.
Common Pitfalls
Off-by-One Errors in Loop Bounds
When iterating through the haystack, the loop should run from 0 to n - m inclusive. A common mistake is using n - m - 1 or n - 1 as the upper bound. The former misses the case where the needle appears at the very end, while the latter causes out-of-bounds access when comparing characters.
Not Handling Empty Needle
When the needle is an empty string, the expected return value is 0 (the empty string is found at the beginning of any string). Forgetting to check for this edge case can lead to incorrect behavior or infinite loops in some implementations.