Algorithm Templates: Arrays & Strings
Arrays and strings are the foundation of coding interviews — you’ll encounter them in nearly every problem set. This page provides battle-tested Python templates for the most important patterns: sliding window, two pointers, binary search on answer, prefix sum, hash maps, and string algorithms like KMP and Manacher. Master these and you’ll have the tools to solve a huge fraction of Medium-level problems.
This template covers the fundamental patterns for array and string problems. Sliding window, two pointers, and prefix sum together solve a huge fraction of Medium problems.
- Beginner’s Guide: LeetCode Beginner’s Guide
Contents
- Sliding Window (fixed/variable)
- Two Pointers (sorted arrays/strings)
- Binary Search on Answer
- Prefix Sum / Difference Array
- Hash Map Frequencies
- KMP (Substring Search)
- Manacher
- Z-Algorithm
- String Rolling Hash
Sliding Window (fixed/variable)
When to use: “longest substring”, “shortest subarray”, “at most k distinct”, or any problem asking for a contiguous subrange that satisfies a constraint.
def longest_no_repeat(s: str) -> int:
cnt = [0] * 256
best = 0
l = 0
for r, ch in enumerate(s):
idx = ord(ch)
cnt[idx] += 1
while cnt[idx] > 1:
cnt[ord(s[l])] -= 1
l += 1
best = max(best, r - l + 1)
return best
| ID | Title | Link | Solution |
|---|---|---|---|
| 3 | Longest Substring Without Repeating Characters | Link | Solution |
| 76 | Minimum Window Substring | Link | - |
| 392 | Is Subsequence | Link | Solution |
| 424 | Longest Repeating Character Replacement | Link | - |
| 616 | Add Bold Tag in String | Link | Solution |
| 681 | Next Closest Time | Link | Solution |
| 713 | Subarray Product Less Than K | Link | Solution |
| 2461 | Maximum Sum of Distinct Subarrays With Length K | Link | Solution |
Two Pointers (sorted arrays/strings)
When to use: “pair with target sum in sorted array”, “container with most water”, “valid palindrome”, or when the array is sorted and you can shrink the search space from both ends.
def two_sum_sorted(a: list[int], target: int) -> bool:
l, r = 0, len(a) - 1
while l < r:
s = a[l] + a[r]
if s == target:
return True
if s < target:
l += 1
else:
r -= 1
return False
| ID | Title | Link | Solution |
|---|---|---|---|
| 15 | 3Sum | Link | - |
| 11 | Container With Most Water | Link | - |
| 125 | Valid Palindrome | Link | - |
| 1768 | Merge Strings Alternately | Link | Solution |
Binary Search on Answer (monotonic predicate)
When to use: “minimize the maximum”, “feasibility check”, “minimum speed/capacity”, or when the answer has a monotonic property (if x works, then x+1 also works).
def first_good(lo: int, hi: int, good) -> int:
# Finds smallest x in [lo, hi] with good(x) == True.
while lo < hi:
mid = (lo + hi) // 2
if good(mid):
hi = mid
else:
lo = mid + 1
return lo
| ID | Title | Link | Solution |
|---|---|---|---|
| 33 | Search in Rotated Sorted Array | Link | Solution |
| 34 | Find First and Last Position of Element in Sorted Array | Link | - |
| 162 | Find Peak Element | Link | - |
| 875 | Koko Eating Bananas | Link | - |
| 1870 | Minimum Speed to Arrive on Time | Link | Solution |
Prefix Sum / Difference Array
When to use: “range sum query”, “subarray sum equals k”, “number of subarrays with sum”, or when you need O(1) range queries after O(n) preprocessing.
def prefix_sum(nums: list[int]) -> list[int]:
ps = [0] * (len(nums) + 1)
for i, x in enumerate(nums):
ps[i + 1] = ps[i] + x
return ps
def range_sum(ps: list[int], l: int, r: int) -> int:
# inclusive range [l, r]
return ps[r + 1] - ps[l]
| ID | Title | Link | Solution |
|---|---|---|---|
| 303 | Range Sum Query - Immutable | Link | Solution |
| 523 | Continuous Subarray Sum | Link | Solution |
| 560 | Subarray Sum Equals K | Link | - |
| 238 | Product of Array Except Self | Link | - |
| 525 | Contiguous Array | Link | Solution |
| 1177 | Can Make Palindrome from Substring | Link | Solution |
| 370 | Range Addition | Link | - |
| 134 | Gas Station | Link | Solution |
| 2270 | Number of Ways to Split Array | Link | Solution |
Hash Map Frequencies
When to use: “two sum”, “group anagrams”, “frequency count”, “contains duplicate”, or any problem where you need O(1) lookups by value.
from collections import Counter
def freq_map(nums: list[int]) -> dict[int, int]:
return dict(Counter(nums))
def freq_map_manual(nums: list[int]) -> dict[int, int]:
freq = {}
for x in nums:
freq[x] = freq.get(x, 0) + 1
return freq
| ID | Title | Link | Solution |
|---|---|---|---|
| 1 | Two Sum | Link | - |
| 49 | Group Anagrams | Link | - |
| 242 | Valid Anagram | Link | Solution |
| 217 | Contains Duplicate | Link | Solution |
| 219 | Contains Duplicate II | Link | Solution |
| 383 | Ransom Note | Link | Solution |
| 981 | Time Based Key-Value Store | Link | - |
| 359 | Logger Rate Limiter | Link | - |
| 2365 | Task Scheduler II | Link | Solution |
| 2342 | Max Sum of a Pair With Equal Sum of Digits | Link | Solution |
KMP (Substring Search)
When to use: “find pattern in string”, “shortest palindrome by prepending”, “repeated string match”, or when you need O(n + m) exact pattern matching.
KMP is a pattern matching algorithm that finds occurrences of a pattern string P within a text string T efficiently — without re-checking characters that are already known to match.
While a naive substring search checks character-by-character and backtracks when a mismatch occurs (worst case O(n * m)), KMP preprocesses the pattern to know how far it can safely skip ahead when mismatches happen.
It does this using a “prefix function” (also called LPS — longest prefix which is also suffix).
Steps
Preprocess the pattern to build the lps[] array.
-
lps[i] = the length of the longest proper prefix of the substring P[0..i] which is also a suffix of this substring.
-
Proper prefix = prefix ≠ the string itself.
Use the LPS array during the search
- When mismatch occurs, instead of resetting j = 0, we move j back to lps[j-1].
def kmp_pi(s: str) -> list[int]:
n = len(s)
pi = [0] * n
for i in range(1, n):
j = pi[i - 1]
while j > 0 and s[i] != s[j]:
j = pi[j - 1]
if s[i] == s[j]:
j += 1
pi[i] = j
return pi
def kmp_find_all(text: str, pattern: str) -> list[int]:
if not pattern:
return list(range(len(text) + 1))
pi = kmp_pi(pattern)
out = []
j = 0
for i, ch in enumerate(text):
while j > 0 and ch != pattern[j]:
j = pi[j - 1]
if ch == pattern[j]:
j += 1
if j == len(pattern):
out.append(i - len(pattern) + 1)
j = pi[j - 1]
return out
| ID | Title | Link | Solution |
|---|---|---|---|
| 28 | Find the Index of the First Occurrence in a String | Link | - |
| 214 | Shortest Palindrome | Link | - |
| 686 | Repeated String Match | Link | Solution |
Manacher (Longest Palindromic Substring, O(n))
When to use: “longest palindromic substring” when O(n) time is required, or counting all palindromic substrings efficiently.
def manacher(s: str) -> str:
if not s:
return ""
t = "|" + "|".join(s) + "|"
n = len(t)
p = [0] * n
center = right = 0
best_len = best_center = 0
for i in range(n):
mirror = 2 * center - i
if i < right:
p[i] = min(right - i, p[mirror])
while (
i - 1 - p[i] >= 0
and i + 1 + p[i] < n
and t[i - 1 - p[i]] == t[i + 1 + p[i]]
):
p[i] += 1
if i + p[i] > right:
center, right = i, i + p[i]
if p[i] > best_len:
best_len, best_center = p[i], i
start = (best_center - best_len) // 2
return s[start : start + best_len]
| ID | Title | Link | Solution |
|---|---|---|---|
| 5 | Longest Palindromic Substring | Link | - |
Z-Algorithm (Pattern occurrences)
When to use: “find all pattern occurrences”, “longest happy prefix”, or as an alternative to KMP for pattern matching.
def z_func(s: str) -> list[int]:
n = len(s)
if n == 0:
return []
z = [0] * n
l = r = 0
for i in range(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] += 1
if i + z[i] - 1 > r:
l = i
r = i + z[i] - 1
return z
| ID | Title | Link | Solution |
|---|---|---|---|
| 1392 | Longest Happy Prefix | Link | - |
String Rolling Hash (Rabin–Karp)
When to use: “repeated DNA sequences”, “longest duplicate substring”, or when you need to compare many substrings in O(1) each after O(n) preprocessing.
class RH:
B = 911382323
M = 1_000_000_007
def __init__(self, s: str):
n = len(s)
self.p = [1] * (n + 1)
self.h = [0] * (n + 1)
for i, ch in enumerate(s):
self.p[i + 1] = (self.p[i] * self.B) % self.M
self.h[i + 1] = (self.h[i] * self.B + ord(ch)) % self.M
def get(self, l: int, r: int) -> int:
# substring hash for s[l:r]
return (self.h[r] - self.h[l] * self.p[r - l]) % self.M
| ID | Title | Link | Solution |
|---|---|---|---|
| 187 | Repeated DNA Sequences | Link | - |
| 686 | Repeated String Match | Link | Solution |
| 1044 | Longest Duplicate Substring | Link | - |
Summary
| Pattern | Signal Phrases | Time |
|---|---|---|
| Sliding Window | “substring”, “subarray”, “at most k” | O(n) |
| Two Pointers | “sorted”, “pair”, “container” | O(n) |
| Binary Search on Answer | “minimize max”, “feasibility” | O(n log range) |
| Prefix Sum | “range sum”, “subarray sum” | O(n) build, O(1) query |
| Hash Map | “frequency”, “group”, “two sum” | O(n) |
| KMP | “pattern in string” | O(n + m) |
More templates
- Data structures (prefix sum, monotonic stack): Data Structures & Core Algorithms
- Graph, Search: Graph, Search
- Master index: Categories & Templates