Complete LeetCode Patterns Guide
A comprehensive guide to all major algorithm patterns for technical interview preparation.
1. Two Pointers
When to Use
- Sorted arrays
- Finding pairs/triplets
- Removing duplicates in-place
- Palindrome checking
- Linked list cycle detection
- Finding middle element
Sub-Patterns
A) Opposite Direction
Start from both ends, move toward center.
Example: Two Sum II (Sorted Array)
def twoSum(nums, target):
left, right = 0, len(nums) - 1
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1 # need larger sum
else:
right -= 1 # need smaller sum
return []
Example: Valid Palindrome
def isPalindrome(s):
left, right = 0, len(s) - 1
while left < right:
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
Example: Container With Most Water
def maxArea(height):
left, right = 0, len(height) - 1
max_water = 0
while left < right:
width = right - left
current_height = min(height[left], height[right])
max_water = max(max_water, width * current_height)
# Move pointer with smaller height
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_water
B) Fast and Slow (Floyd's Algorithm)
Both pointers move in same direction at different speeds.
Example: Linked List Cycle Detection
def hasCycle(head):
if not head:
return False
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
Example: Find Middle of Linked List
def middleNode(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
C) Same Direction with Gap
Maintain fixed distance between pointers.
Example: Remove Nth Node From End
def removeNthFromEnd(head, n):
dummy = ListNode(0)
dummy.next = head
fast = slow = dummy
# Move fast n+1 steps ahead
for _ in range(n + 1):
fast = fast.next
# Move both until fast reaches end
while fast:
fast = fast.next
slow = slow.next
# Remove node
slow.next = slow.next.next
return dummy.next
D) Same Direction (Write Pointer)
One pointer scans, another writes.
Example: Remove Duplicates from Sorted Array
def removeDuplicates(nums):
if not nums:
return 0
write_pos = 1
for i in range(1, len(nums)):
if nums[i] != nums[i-1]:
nums[write_pos] = nums[i]
write_pos += 1
return write_pos
Example: Move Zeroes
def moveZeroes(nums):
write_pos = 0
# Move all non-zeros to front
for i in range(len(nums)):
if nums[i] != 0:
nums[write_pos] = nums[i]
write_pos += 1
# Fill rest with zeros
for i in range(write_pos, len(nums)):
nums[i] = 0
Key Insights
- Opposite direction: Works on sorted data, makes directional decisions
- Fast/slow: Detects cycles, finds middle elements
- Gap: Useful for "Nth from end" problems
- Write pointer: In-place array modifications
2. Hash Tables and Sets
When to Use
- "Does X exist?"
- "Have I seen this before?"
- Counting frequencies
- Finding duplicates
- Complement searches
- Grouping/categorizing data
Mental Model
Trade space for speed - remember past elements for O(1) lookups.
Example: Two Sum (Unsorted)
def twoSum(nums, target):
seen = {} # value -> index
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
Example: First Unique Character
def firstUniqChar(s):
count = {}
# Count frequencies
for char in s:
count[char] = count.get(char, 0) + 1
# Find first with count 1
for i, char in enumerate(s):
if count[char] == 1:
return i
return -1
Example: Group Anagrams
from collections import defaultdict
def groupAnagrams(strs):
groups = defaultdict(list)
for s in strs:
# Sort as key (or use char count tuple)
key = ''.join(sorted(s))
groups[key].append(s)
return list(groups.values())
Example: Longest Consecutive Sequence
def longestConsecutive(nums):
num_set = set(nums)
max_length = 0
for num in num_set:
# Only start counting if it's the start of a sequence
if num - 1 not in num_set:
current = num
length = 1
while current + 1 in num_set:
current += 1
length += 1
max_length = max(max_length, length)
return max_length
Key Insights
- Use hash map when you need to remember and lookup
- Use hash set for existence checks
- Count frequencies for duplicate/unique problems
- Time: O(1) average, Space: O(n)
3. Stack
When to Use
- Parentheses/bracket matching
- "Next greater/smaller element"
- Evaluating expressions
- Undo operations
- Processing in reverse order
- Nested structures
Mental Model
LIFO (Last In, First Out) - like a pile of plates.
Example: Valid Parentheses
def isValid(s):
stack = []
pairs = {'(': ')', '[': ']', '{': '}'}
for char in s:
if char in pairs: # opening bracket
stack.append(char)
else: # closing bracket
if not stack or pairs[stack.pop()] != char:
return False
return len(stack) == 0
Example: Evaluate Reverse Polish Notation
def evalRPN(tokens):
stack = []
for token in tokens:
if token in ['+', '-', '*', '/']:
b = stack.pop()
a = stack.pop()
if token == '+':
stack.append(a + b)
elif token == '-':
stack.append(a - b)
elif token == '*':
stack.append(a * b)
else:
stack.append(int(a / b))
else:
stack.append(int(token))
return stack[0]
Example: Min Stack
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val):
self.stack.append(val)
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
def pop(self):
val = self.stack.pop()
if val == self.min_stack[-1]:
self.min_stack.pop()
def top(self):
return self.stack[-1]
def getMin(self):
return self.min_stack[-1]
Example: Daily Temperatures
def dailyTemperatures(temperatures):
result = [0] * len(temperatures)
stack = [] # indices
for i, temp in enumerate(temperatures):
while stack and temperatures[stack[-1]] < temp:
prev_idx = stack.pop()
result[prev_idx] = i - prev_idx
stack.append(i)
return result
Example: Simplify Path
def simplifyPath(path):
stack = []
parts = path.split('/')
for part in parts:
if part == '..' and stack:
stack.pop()
elif part and part != '.' and part != '..':
stack.append(part)
return '/' + '/'.join(stack)
Key Insights
- Opening elements → push
- Closing elements → pop and verify
- "Next greater" problems → monotonic stack pattern
- Nested structures → natural stack use case
4. Tree Traversal (DFS & BFS)
When to Use
- Any tree/hierarchical structure
- Finding paths
- Level-order problems
- Validation problems
- Calculating depths/heights
A) DFS (Depth-First Search)
Go deep first, explore one path completely before backtracking.
Recursive DFS (Most Common)
Example: Maximum Depth
def maxDepth(root):
if not root:
return 0
left_depth = maxDepth(root.left)
right_depth = maxDepth(root.right)
return 1 + max(left_depth, right_depth)
Example: Validate Binary Search Tree
def isValidBST(root):
def validate(node, min_val, max_val):
if not node:
return True
if not (min_val < node.val < max_val):
return False
return (validate(node.left, min_val, node.val) and
validate(node.right, node.val, max_val))
return validate(root, float('-inf'), float('inf'))
Example: Path Sum
def hasPathSum(root, targetSum):
if not root:
return False
# Leaf node
if not root.left and not root.right:
return root.val == targetSum
remaining = targetSum - root.val
return (hasPathSum(root.left, remaining) or
hasPathSum(root.right, remaining))
Example: Lowest Common Ancestor
def lowestCommonAncestor(root, p, q):
if not root or root == p or root == q:
return root
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if left and right:
return root
return left if left else right
Example: Diameter of Binary Tree
def diameterOfBinaryTree(root):
max_diameter = 0
def height(node):
nonlocal max_diameter
if not node:
return 0
left_height = height(node.left)
right_height = height(node.right)
# Update diameter
max_diameter = max(max_diameter, left_height + right_height)
return 1 + max(left_height, right_height)
height(root)
return max_diameter
Iterative DFS (Using Stack)
Example: Inorder Traversal
def inorderTraversal(root):
result = []
stack = []
current = root
while current or stack:
# Go to leftmost node
while current:
stack.append(current)
current = current.left
# Process node
current = stack.pop()
result.append(current.val)
# Go right
current = current.right
return result
B) BFS (Breadth-First Search)
Process level by level using a queue.
Example: Level Order Traversal
from collections import deque
def levelOrder(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
level = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result
Example: Right Side View
def rightSideView(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
for i in range(level_size):
node = queue.popleft()
# Last node in level
if i == level_size - 1:
result.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
Example: Minimum Depth
def minDepth(root):
if not root:
return 0
queue = deque([(root, 1)])
while queue:
node, depth = queue.popleft()
# First leaf found = minimum depth
if not node.left and not node.right:
return depth
if node.left:
queue.append((node.left, depth + 1))
if node.right:
queue.append((node.right, depth + 1))
return 0
Key Insights
- DFS: Use for paths, validation, heights (recursive is cleaner)
- BFS: Use for shortest paths, level-order, minimum depth
- DFS Time/Space: O(n) time, O(h) space where h = height
- BFS Time/Space: O(n) time, O(w) space where w = max width
5. Linked List Manipulation
When to Use
- Reversing lists
- Reordering nodes
- Merging lists
- Detecting cycles
- Finding intersections
Mental Model
Keep track of multiple pointers to avoid losing references.
Example: Reverse Linked List
def reverseList(head):
prev = None
curr = head
while curr:
next_temp = curr.next # save next
curr.next = prev # reverse link
prev = curr # move prev forward
curr = next_temp # move curr forward
return prev
Example: Merge Two Sorted Lists
def mergeTwoLists(l1, l2):
dummy = ListNode(0)
current = dummy
while l1 and l2:
if l1.val < l2.val:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next
# Attach remaining nodes
current.next = l1 if l1 else l2
return dummy.next
Example: Reorder List
def reorderList(head):
if not head or not head.next:
return
# Find middle
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
second = slow.next
slow.next = None
prev = None
while second:
next_temp = second.next
second.next = prev
prev = second
second = next_temp
# Merge alternating
first, second = head, prev
while second:
temp1, temp2 = first.next, second.next
first.next = second
second.next = temp1
first, second = temp1, temp2
Example: Palindrome Linked List
def isPalindrome(head):
# Find middle
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
second = slow.next
slow.next = None
prev = None
while second:
next_temp = second.next
second.next = prev
prev = second
second = next_temp
# Compare
first, second = head, prev
while second:
if first.val != second.val:
return False
first = first.next
second = second.next
return True
Example: Add Two Numbers
def addTwoNumbers(l1, l2):
dummy = ListNode(0)
current = dummy
carry = 0
while l1 or l2 or carry:
val1 = l1.val if l1 else 0
val2 = l2.val if l2 else 0
total = val1 + val2 + carry
carry = total // 10
current.next = ListNode(total % 10)
current = current.next
l1 = l1.next if l1 else None
l2 = l2.next if l2 else None
return dummy.next
Key Insights
- Use dummy node to simplify edge cases
- Save
nextbefore breaking links - Three pointers for reversal:
prev,curr,next - Combine with fast/slow for complex operations
6. Sliding Window
When to Use
- Longest/shortest substring/subarray
- Fixed-size window problems
- "All subarrays of size K"
- Contiguous sequences with constraints
Mental Model
Maintain a window that expands (right pointer) and contracts (left pointer) to satisfy conditions.
A) Fixed Size Window
Example: Maximum Sum Subarray of Size K
def maxSumSubarray(nums, k):
window_sum = sum(nums[:k])
max_sum = window_sum
for i in range(k, len(nums)):
# Slide window: remove left, add right
window_sum = window_sum - nums[i-k] + nums[i]
max_sum = max(max_sum, window_sum)
return max_sum
B) Variable Size Window
Example: Longest Substring Without Repeating Characters
def lengthOfLongestSubstring(s):
seen = set()
left = 0
max_length = 0
for right in range(len(s)):
# Shrink window while duplicate exists
while s[right] in seen:
seen.remove(s[left])
left += 1
seen.add(s[right])
max_length = max(max_length, right - left + 1)
return max_length
Example: Minimum Window Substring
from collections import Counter
def minWindow(s, t):
if not s or not t:
return ""
need = Counter(t)
have = {}
left = 0
min_len = float('inf')
min_start = 0
matched = 0
for right in range(len(s)):
char = s[right]
have[char] = have.get(char, 0) + 1
if char in need and have[char] == need[char]:
matched += 1
# Try to shrink window
while matched == len(need):
if right - left + 1 < min_len:
min_len = right - left + 1
min_start = left
left_char = s[left]
have[left_char] -= 1
if left_char in need and have[left_char] < need[left_char]:
matched -= 1
left += 1
return "" if min_len == float('inf') else s[min_start:min_start + min_len]
Example: Longest Repeating Character Replacement
def characterReplacement(s, k):
count = {}
left = 0
max_length = 0
max_count = 0
for right in range(len(s)):
count[s[right]] = count.get(s[right], 0) + 1
max_count = max(max_count, count[s[right]])
# Window size - most frequent char > k replacements allowed
while (right - left + 1) - max_count > k:
count[s[left]] -= 1
left += 1
max_length = max(max_length, right - left + 1)
return max_length
Example: Permutation in String
from collections import Counter
def checkInclusion(s1, s2):
if len(s1) > len(s2):
return False
s1_count = Counter(s1)
window_count = Counter(s2[:len(s1)])
if s1_count == window_count:
return True
for i in range(len(s1), len(s2)):
# Add new char
window_count[s2[i]] += 1
# Remove old char
left_char = s2[i - len(s1)]
window_count[left_char] -= 1
if window_count[left_char] == 0:
del window_count[left_char]
if s1_count == window_count:
return True
return False
Key Insights
- Fixed window: Simple sliding, calculate on each slide
- Variable window: Expand with right, contract with left
- Use hash map/set to track window contents
- Time: O(n), Space: O(k) where k = unique elements
7. Binary Search
When to Use
- Sorted arrays
- "Find target/boundary/peak"
- Minimizing/maximizing with constraint
- "Can we achieve X?" problems
Mental Model
Eliminate half of search space each iteration.
A) Classic Binary Search
Example: Find Target
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
B) Finding Boundaries
Example: First and Last Position
def searchRange(nums, target):
def findBoundary(is_first):
left, right = 0, len(nums) - 1
result = -1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
result = mid
if is_first:
right = mid - 1 # search left half
else:
left = mid + 1 # search right half
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return result
return [findBoundary(True), findBoundary(False)]
C) Search in Rotated Array
Example: Search in Rotated Sorted Array
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
# Left half is sorted
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# Right half is sorted
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
D) Find Peak Element
Example: Find Peak
def findPeakElement(nums):
left, right = 0, len(nums) - 1
while left < right:
mid = left + (right - left) // 2
if nums[mid] > nums[mid + 1]:
# Peak is on left (or mid itself)
right = mid
else:
# Peak is on right
left = mid + 1
return left
E) Minimize/Maximize Problems
Example: Minimum in Rotated Sorted Array
def findMin(nums):
left, right = 0, len(nums) - 1
while left < right:
mid = left + (right - left) // 2
if nums[mid] > nums[right]:
# Minimum is in right half
left = mid + 1
else:
# Minimum is in left half (or mid itself)
right = mid
return nums[left]
Example: Koko Eating Bananas
import math
def minEatingSpeed(piles, h):
def canFinish(speed):
hours = sum(math.ceil(pile / speed) for pile in piles)
return hours <= h
left, right = 1, max(piles)
while left < right:
mid = left + (right - left) // 2
if canFinish(mid):
right = mid # try slower speed
else:
left = mid + 1 # need faster speed
return left
Key Insights
- Use
left + (right - left) // 2to avoid overflow - left <= right for finding exact value
- left < right for finding boundary
- "Can we achieve X?" → Binary search on answer space
- Time: O(log n)
8. Prefix Sum
When to Use
- Subarray sum problems
- Range sum queries
- "Count subarrays with sum = K"
- Problems with negative numbers
Mental Model
Precompute cumulative sums to answer range queries in O(1).
Example: Subarray Sum Equals K
def subarraySum(nums, k):
count = 0
prefix_sum = 0
sum_count = {0: 1} # prefix_sum -> count
for num in nums:
prefix_sum += num
# Check if there's a previous prefix that gives us k
if prefix_sum - k in sum_count:
count += sum_count[prefix_sum - k]
sum_count[prefix_sum] = sum_count.get(prefix_sum, 0) + 1
return count
Example: Contiguous Array (Equal 0s and 1s)
def findMaxLength(nums):
# Treat 0 as -1, find subarray with sum 0
max_length = 0
sum_index = {0: -1} # sum -> first index
running_sum = 0
for i, num in enumerate(nums):
running_sum += 1 if num == 1 else -1
if running_sum in sum_index:
max_length = max(max_length, i - sum_index[running_sum])
else:
sum_index[running_sum] = i
return max_length
Example: Range Sum Query (Immutable)
class NumArray:
def __init__(self, nums):
self.prefix = [0]
for num in nums:
self.prefix.append(self.prefix[-1] + num)
def sumRange(self, left, right):
return self.prefix[right + 1] - self.prefix[left]
Example: Product of Array Except Self
def productExceptSelf(nums):
n = len(nums)
result = [1] * n
# Left products
left_product = 1
for i in range(n):
result[i] = left_product
left_product *= nums[i]
# Right products
right_product = 1
for i in range(n - 1, -1, -1):
result[i] *= right_product
right_product *= nums[i]
return result
Key Insights
- Prefix sum:
sum(i, j) = prefix[j+1] - prefix[i] - Use hash map to store prefix sums for subarray problems
- Works with negative numbers (unlike sliding window)
- Time: O(n), Space: O(n)
9. Graph Traversal
When to Use
- Connected components
- Cycle detection
- Shortest paths
- Topological sorting
- Dependencies (course schedule)
A) DFS on Graph
Example: Number of Islands
def numIslands(grid):
if not grid:
return 0
def dfs(r, c):
if (r < 0 or r >= len(grid) or c < 0
## The Recognition Framework
Most easy-mid problems can be solved by asking yourself these questions in order:
1. Does this involve a sorted array or finding pairs/triplets?
→ Two Pointers
2. Do I need to check if something exists or count frequencies?
→ Hash Table/Set
3. Does this involve parentheses, operations in reverse order, or "most recent" logic?
→ Stack
4. Is this about a tree or graph structure?
→ DFS (Recursion) or BFS (Queue)
5. Does this involve reversing or reordering a linked list?
→ Three-Pointer Technique