Randomisation by default via PYTHONHASHSEED). This is a security measure against HashDoS attacks. For persistent hashing (e.g., on-disk hash tables), use hashlib or a deterministic hash function.
Compare the pattern against every possible position in the text.
def naive_search ( text , pattern ):
Time: O(n*m) worst case, O(n+m) best case
n, m = len (text), len (pattern)
for i in range (n - m + 1 ):
if text[i + j] != pattern[j]:
Use hashing to compare the pattern with substrings in O ( 1 ) O(1) O ( 1 ) expected time per position.
def rabin_karp ( text , pattern , base = 256 , mod = 10 ** 9 + 7 ):
Rabin-Karp string matching using rolling hash.
Time: O(n+m) average, O(nm) worst case
n, m = len (text), len (pattern)
base_m = 1 # base^(m-1) % mod
pattern_hash = (pattern_hash * base + ord (pattern[i])) % mod
text_hash = (text_hash * base + ord (text[i])) % mod
base_m = (base_m * base) % mod
for i in range (n - m + 1 ):
if text_hash == pattern_hash:
# Verify to avoid false positives (hash collision)
if text[i : i + m] == pattern:
# Rolling hash: remove leading char, add trailing char
(text_hash - ord (text[i]) * base_m) * base + ord (text[i + m])
text_hash = (text_hash + mod) % mod # ensure non-negative
Complexity: O ( n + m ) O(n+m) O ( n + m ) average case, O ( n m ) O(nm) O ( nm ) worst case (all positions are hash matches requiring Verification). Using two independent hash functions reduces the probability of false positives to Negligible levels.
KMP preprocesses the pattern to build a “failure function” (also called the LPS array, longest Proper prefix which is also suffix) that allows the algorithm to skip redundant comparisons.
Build the Longest Proper Prefix-Suffix array for KMP.
length = 0 # length of the previous longest prefix suffix
if pattern[i] == pattern[length]:
def kmp_search ( text , pattern ):
Knuth-Morris-Pratt string matching.
Time: O(n + m) worst case
n, m = len (text), len (pattern)
if text[i] == pattern[j]:
return i - j # match found
Complexity: O ( n + m ) O(n + m) O ( n + m ) worst case, guaranteed linear time, no hash collisions to worry about. The LPS array ensures the text pointer i never moves backward.
Check if t is an anagram of s.
Time: O(n), Space: O(1), fixed alphabet size
counts = [ 0 ] * 26 # assuming lowercase English letters
counts[ ord (c) - ord ( ' a ' )] += 1
counts[ ord (c) - ord ( ' a ' )] -= 1
if counts[ ord (c) - ord ( ' a ' )] < 0 :
from collections import defaultdict
def group_anagrams ( strings ):
Group strings by anagram equivalence.
Time: O(n * k * log k) where k = max string length
groups = defaultdict( list )
return list (groups.values())
An alternative using character counts as keys (better for long strings with small alphabets):
def group_anagrams_count ( strings ):
Group anagrams using character count tuples as keys.
Time: O(n * k) where k = max string length
groups = defaultdict( list )
counts[ ord (c) - ord ( ' a ' )] += 1
groups[ tuple (counts)].append(s)
return list (groups.values())
def spiral_order ( matrix ):
Traverse an m x n matrix in spiral order.
Time: O(m * n), Space: O(1) (excluding output)
if not matrix or not matrix[ 0 ]:
top, bottom = 0 , len (matrix) - 1
left, right = 0 , len (matrix[ 0 ]) - 1
while top <= bottom and left <= right:
for col in range (left, right + 1 ):
result.append(matrix[top][col])
for row in range (top, bottom + 1 ):
result.append(matrix[row][right])
for col in range (right, left - 1 , - 1 ):
result.append(matrix[bottom][col])
for row in range (bottom, top - 1 , - 1 ):
result.append(matrix[row][left])
def diagonal_traverse ( matrix ):
Traverse an m x n matrix in diagonal zigzag order.
Time: O(m * n), Space: O(1) (excluding output)
if not matrix or not matrix[ 0 ]:
m, n = len (matrix), len (matrix[ 0 ])
while len (result) < m * n:
while row >= 0 and col < n:
result.append(matrix[row][col])
while col >= 0 and row < m:
result.append(matrix[row][col])
Partition an array into three sections (e.g., values less than, equal to, and greater than a pivot) In a single pass.
def dutch_national_flag ( arr , pivot_idx ):
Three-way partition around arr[pivot_idx].
After: arr[0..lt-1] < pivot, arr[lt..gt] == pivot, arr[gt+1..n-1] > pivot
arr[lt], arr[i] = arr[i], arr[lt]
arr[gt], arr[i] = arr[i], arr[gt]
# Don't increment i, need to examine the swapped element
return lt, gt # boundaries of the equal region
def merge_sorted_arrays ( a , b ):
Merge two sorted arrays into one sorted array.
Time: O(n + m), Space: O(n + m)
while i < len (a) and j < len (b):
def merge_in_place ( a , b ):
Merge sorted array b into sorted array a.
Assumes a has enough buffer at the end to hold b.
Time: O(n + m), Space: O(1)
total = len (a) + len (b) - 2 # subtract buffer count
i = len (a) - 1 # last real element in a
def majority_element ( arr ):
Find the element that appears more than n/2 times (if it exists).
Boyer-Moore voting algorithm.
# Verify (the algorithm only finds a candidate)
if arr.count(candidate) > len (arr) // 2 :
Sliding window problems are rife with off-by-one errors. The most common: confusing “exclusive upper Bound” with “inclusive upper bound.” Decide on a convention (Python-style [left, right) is Recommended) and stick to it consistently. The number of elements in the window is always right - leftNever right - left + 1.
Rabin-Karp’s rolling hash computation involves multiplication and addition that can overflow 64-bit Integers for large inputs. Always use modular arithmetic with a large prime modulus (or use Python’s Arbitrary-precision integers, which do not overflow). For production use, consider using two Independent hash functions to reduce collision probability.
Empty arrays, single-element arrays, arrays with all identical elements, and arrays where no valid Pair exists are all edge cases that two-pointer solutions must handle. Test your solution with [] [1]``[1, 1, 1, 1]And a case where no solution exists.
In a world of UTF-8, string indexing does not give you bytes, it gives you code points. Reversing a UTF-8 string by swapping bytes produces invalid UTF-8. Reversing by code points is safe but does not Handle grapheme clusters (e.g., the emoji flags sequence). Use language-appropriate string reversal.
If you use a mutable object as a hash map key and then mutate it, the object’s hash changes and you can no longer find it in the map. In Python, this manifests as a dict key becoming invisible after Mutation. Always use immutable types (tuples, frozensets, strings) as keys, or ensure keys are never Mutated after insertion.
For large arrays, prefix sums can overflow 32-bit integers. A sum of 10 6 10^6 1 0 6 elements each up to 10 9 10^9 1 0 9 gives 10 15 10^{15} 1 0 15 Which exceeds 32-bit range. Use 64-bit integers (int in Python is always Arbitrary precision, but in C/C++/Java, use long long/long).
In sliding window problems, “at most k distinct elements” requires shrinking the window when the Count exceeds k k k While “exactly k distinct elements” requires maintaining two windows (one for at most k k k and one for at most k − 1 k-1 k − 1 ). Conflating these leads to incorrect solutions.
flowchart TD
A[Arrays And Strings] --> B[Key Concepts]
A --> C[Core Principles]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
C --> F[Design patterns]
D --> G[Real-world usage] This topic covers the core concepts of arrays and strings, including underlying theory, practical implementation, and key applications.
Key concepts include:
Big O notation and complexity analysis searching algorithms (binary, linear) sorting algorithms (bubble, merge, quick) graph algorithms (Dijkstra, BFS, DFS) dynamic programming Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.