How to Solve LeetCode Problems: The Pattern Recognition Approach
Stop grinding LeetCode blindly. Learn the 15 core patterns, how to identify them, and a systematic approach to solving coding interview problems.
There are over 3,000 problems on LeetCode. If you're solving them one by one, hoping that eventually you'll have seen "enough," you're doing it wrong. That's not preparation -- that's gambling that the exact problem you practiced will show up in your interview.
The developers who consistently pass coding interviews aren't the ones who memorized the most solutions. They're the ones who recognize patterns. They read a problem, identify which pattern applies, and adapt a known approach to the specific constraints. This is a learnable skill, and it's far more effective than raw volume.
Why Pattern Recognition Beats Grinding
When you solve 500 random LeetCode problems, you might learn 500 individual solutions. When you study 15 patterns, you can solve thousands of problems because most problems are variations of these patterns. The difference is like memorizing every multiplication fact versus understanding how multiplication works.
Here's what happens in an interview when you recognize patterns:
- You read the problem
- You identify keywords and constraints that signal a pattern
- You recall the pattern's template
- You adapt the template to the specific problem
- You code it, handle edge cases, and analyze complexity
Pattern recognition gives you a systematic approach. Memorization gives you hope.
The 15 Core Patterns
Every competitive programmer and interview coach has a slightly different list, but these 15 cover the vast majority of LeetCode problems you'll encounter in interviews.
1. Two Pointers
Signal: Sorted array, finding pairs, comparing elements from both ends. Template: Two indices moving toward each other or in the same direction.def two_sum_sorted(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
else:
right -= 1
return []
Problems this solves: Two Sum II, 3Sum, Container With Most Water, Trapping Rain Water, Remove Duplicates from Sorted Array.
2. Sliding Window
Signal: Contiguous subarray/substring, maximum/minimum of a range, fixed or variable window size. Template: Expand the window from the right, shrink from the left when a condition is violated.def max_subarray_of_size_k(nums, k):
window_sum = 0
max_sum = 0
window_start = 0
for window_end in range(len(nums)):
window_sum += nums[window_end]
if window_end >= k - 1:
max_sum = max(max_sum, window_sum)
window_sum -= nums[window_start]
window_start += 1
return max_sum
Problems this solves: Maximum Sum Subarray of Size K, Longest Substring Without Repeating Characters, Minimum Window Substring, Fruit Into Baskets.
3. Binary Search
Signal: Sorted array, finding a boundary, "minimum value that satisfies a condition." Template: Narrow the search space by half each iteration.def binary_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
The advanced version -- binary search on the answer -- is even more powerful. When a problem asks for the "minimum X such that condition Y holds," binary search on X.
4. BFS / Level-Order Traversal
Signal: Shortest path in unweighted graph, level-by-level tree processing, spreading/infection problems.from collections import deque
def bfs(graph, start):
visited = {start}
queue = deque([start])
distance = {start: 0}
while queue:
node = queue.popleft()
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
distance[neighbor] = distance[node] + 1
queue.append(neighbor)
return distance
5. DFS / Backtracking
Signal: Explore all possibilities, generate all combinations/permutations, path finding in a graph/matrix.def permutations(nums):
result = []
def backtrack(current, remaining):
if not remaining:
result.append(current[:])
return
for i in range(len(remaining)):
current.append(remaining[i])
backtrack(current, remaining[:i] + remaining[i+1:])
current.pop()
backtrack([], nums)
return result
6. Dynamic Programming (Bottom-Up)
Signal: Optimal substructure, overlapping subproblems, "minimum/maximum/count ways to reach X."def climb_stairs(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1] = 1
dp[2] = 2
for i in range(3, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
7. Hash Map / Counting
Signal: Frequency counting, checking for duplicates, grouping elements, O(1) lookups.def group_anagrams(strs):
groups = {}
for s in strs:
key = tuple(sorted(s))
if key not in groups:
groups[key] = []
groups[key].append(s)
return list(groups.values())
8. Monotonic Stack
Signal: "Next greater/smaller element," stock span, histogram problems.def next_greater_element(nums):
result = [-1] * len(nums)
stack = [] # stores indices
for i in range(len(nums)):
while stack and nums[i] > nums[stack[-1]]:
result[stack.pop()] = nums[i]
stack.append(i)
return result
9. Heap / Priority Queue
Signal: "K largest/smallest," merge K sorted lists, scheduling problems, median maintenance.import heapq
def k_largest(nums, k):
# Min-heap of size k
heap = nums[:k]
heapq.heapify(heap)
for num in nums[k:]:
if num > heap[0]:
heapq.heapreplace(heap, num)
return sorted(heap, reverse=True)
10. Prefix Sum
Signal: Range sum queries, subarray sum equals K, cumulative operations.def subarray_sum_equals_k(nums, k):
count = 0
prefix_sum = 0
seen = {0: 1}
for num in nums:
prefix_sum += num
if prefix_sum - k in seen:
count += seen[prefix_sum - k]
seen[prefix_sum] = seen.get(prefix_sum, 0) + 1
return count
11. Union-Find (Disjoint Set)
Signal: Connected components, "are these two nodes connected?" graph grouping.12. Trie (Prefix Tree)
Signal: Autocomplete, word search, prefix matching, dictionary problems.13. Topological Sort
Signal: Task scheduling, dependency ordering, course prerequisites.14. Greedy
Signal: Local optimal choice leads to global optimum, interval scheduling, activity selection.15. Bit Manipulation
Signal: Single number in array of duplicates, power of 2, XOR tricks.How to Identify the Pattern
This is the critical skill. Here's a decision framework:
Does the problem involve a sorted array or finding pairs? Start with Two Pointers or Binary Search. Does it ask about contiguous subarrays or substrings? Think Sliding Window or Prefix Sum. Is it about trees or graphs? BFS for shortest path or level processing, DFS for traversal and backtracking. Does it ask for all combinations, permutations, or subsets? Backtracking (DFS). Does it ask for the optimal value with overlapping subproblems? Dynamic Programming. Does it mention "K largest" or "K smallest"? Heap. Does it ask about "next greater" or "next smaller"? Monotonic Stack. Does it involve connected components or grouping? Union-Find or BFS/DFS. Does it involve frequencies or lookups? Hash Map.Read the problem constraints carefully. An array of length 10^5 rules out O(n^2) solutions. A grid of size 1000x1000 needs BFS/DFS. Constraints are pattern hints.
Working Through Example Problems
Let's apply this framework to real problems.
Example 1: "Longest Substring Without Repeating Characters"
Read: Given a string, find the longest substring without repeating characters. Identify: "Substring" = contiguous. "Longest" = maximum. This is Sliding Window. Solve:def length_of_longest_substring(s):
char_index = {}
max_length = 0
window_start = 0
for window_end in range(len(s)):
char = s[window_end]
if char in char_index and char_index[char] >= window_start:
window_start = char_index[char] + 1
char_index[char] = window_end
max_length = max(max_length, window_end - window_start + 1)
return max_length
Example 2: "Number of Islands"
Read: Given a 2D grid of '1's (land) and '0's (water), count islands. Identify: Grid traversal, connected components. This is BFS or DFS. Solve:def num_islands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != '1':
return
grid[r][c] = '0' # mark visited
dfs(r+1, c)
dfs(r-1, c)
dfs(r, c+1)
dfs(r, c-1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
dfs(r, c)
return count
Example 3: "Coin Change"
Read: Given coin denominations and a target amount, find the minimum number of coins. Identify: "Minimum" + "ways to reach a target" + overlapping subproblems = Dynamic Programming. Solve:def coin_change(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if coin <= i and dp[i - coin] + 1 < dp[i]:
dp[i] = dp[i - coin] + 1
return dp[amount] if dp[amount] != float('inf') else -1
Example 4: "Merge Intervals"
Read: Given a collection of intervals, merge overlapping ones. Identify: Intervals + sorting. This is a Greedy/Sorting pattern. Solve:def merge_intervals(intervals):
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for current in intervals[1:]:
if current[0] <= merged[-1][1]:
merged[-1][1] = max(merged[-1][1], current[1])
else:
merged.append(current)
return merged
Example 5: "Kth Largest Element in an Array"
Read: Find the kth largest element. Identify: "Kth largest" = Heap. Solve:import heapq
def find_kth_largest(nums, k):
heap = []
for num in nums:
heapq.heappush(heap, num)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]
Time Management During Practice
Not every problem deserves the same amount of time. Here's a framework:
Easy problems: Spend 15-20 minutes maximum. If you can't solve it in 15 minutes, you're missing a fundamental concept. Study the concept, not just the solution. Medium problems: Spend 25-35 minutes. Most interview questions are medium difficulty. You should be able to identify the pattern within 5 minutes and code a solution within 30. Hard problems: Spend 40-50 minutes. If you're stuck after 30 minutes, look at the discussion section for the approach (not the full code), then implement it yourself.When to Look at Solutions
Looking at solutions is not cheating -- it's learning. But timing matters.
Look immediately if: You've never seen the pattern before. There's no point struggling with dynamic programming if you don't know what it is. Read the editorial, understand the pattern, then solve 3-5 similar problems to internalize it. Wait 20-30 minutes if: You know the pattern but can't figure out the specific application. Give yourself time to struggle, because the struggle is where learning happens. Then look at the approach, close the solution, and implement it from memory. Never look if: You've identified the pattern and are making progress, even slowly. The feeling of solving it yourself is irreplaceable for building confidence.Tracking Progress
Don't just count solved problems. Track your pattern coverage:
- Make a spreadsheet with the 15 patterns as rows
- For each pattern, aim to solve 5-8 problems at increasing difficulty
- Mark each problem as: solved independently, solved with hints, or needed solution
- Revisit "needed solution" problems after a week
- When you can solve 3 consecutive problems in a pattern without help, move on
Common Mistakes
Jumping to code too fast. Spend 3-5 minutes understanding the problem, identifying the pattern, and planning your approach before writing a single line. The time investment pays for itself in fewer rewrites. Ignoring constraints. If the array length is 10^4, both O(n log n) and O(n^2) might work. If it's 10^5, only O(n log n) or better will pass. Constraints tell you which patterns are viable. Memorizing solutions instead of patterns. If you can solve "Trapping Rain Water" but can't solve a different monotonic stack problem, you memorized a solution, not a pattern. Only solving Easy problems. Easy problems build syntax comfort but rarely teach the patterns you need for interviews. Most interview questions are Medium difficulty. Spend 70% of your time on Mediums. Practicing without time pressure. In an interview, you have 30-45 minutes per problem. Practice under time constraints at least some of the time.What's Next
Start with the patterns you're weakest at, not the ones you enjoy most. If you can already solve Two Pointer problems in your sleep but Dynamic Programming makes you anxious, guess which one you should practice.
Build a study plan: 2-3 problems per day, organized by pattern, for 6-8 weeks. That's roughly 100-125 problems with full pattern coverage. Supplement with weekly mock interviews to practice the communication and time management that pure LeetCode grinding doesn't teach.
The goal isn't to solve every LeetCode problem. The goal is to recognize any problem as a variation of a pattern you understand deeply.
Practice pattern-based problem solving and sharpen your DSA skills at CodeUp.