DSA Patterns Cheatsheet for LeetCode
DSA Patterns Cheatsheet for LeetCode
md 2025-07-02
1. Two Pointers
When to use: Problems involving sorted arrays, pairs, triplets, or subarrays.
Pattern Recognition:
Template:
1 / 60
[Link] 2025-07-02
def two_pointers(arr):
left, right = 0, len(arr) - 1
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1
else:
right -= 1
return []
return []
def three_sum(nums):
[Link]()
result = []
if current_sum == 0:
2 / 60
[Link] 2025-07-02
# Skip duplicates
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif current_sum < 0:
left += 1
else:
right -= 1
return result
Practice Problems:
2. Sliding Window
When to use: Problems involving contiguous subarrays/substrings with specific conditions.
Pattern Recognition:
3 / 60
[Link] 2025-07-02
return max_sum
# Update result
result = max(result, right - left + 1)
return result
def length_of_longest_substring(s):
char_index = {}
left = 0
max_length = 0
char_index[s[right]] = right
max_length = max(max_length, right - left + 1)
return max_length
4 / 60
[Link] 2025-07-02
# Count characters in t
dict_t = {}
for char in t:
dict_t[char] = dict_t.get(char, 0) + 1
required = len(dict_t)
left, right = 0, 0
formed = 0
window_counts = {}
window_counts[char] -= 1
if char in dict_t and window_counts[char] < dict_t[char]:
formed -= 1
left += 1
right += 1
Practice Problems:
Pattern Recognition:
Template:
def has_cycle(head):
if not head or not [Link]:
return False
if slow == fast:
return True
return False
def detect_cycle(head):
if not head or not [Link]:
return None
return slow
6 / 60
[Link] 2025-07-02
def find_middle(head):
slow = fast = head
return slow
def is_palindrome(head):
if not head or not [Link]:
return True
# Find middle
slow = fast = head
while [Link] and [Link]:
slow = [Link]
fast = [Link]
# Compare
first_half = head
while second_half:
if first_half.val != second_half.val:
return False
first_half = first_half.next
second_half = second_half.next
return True
def reverse_list(head):
prev = None
current = head
while current:
next_temp = [Link]
[Link] = prev
prev = current
current = next_temp
return prev
7 / 60
[Link] 2025-07-02
Happy Number:
def is_happy(n):
def get_sum_of_squares(num):
total_sum = 0
while num > 0:
digit = num % 10
total_sum += digit * digit
num //= 10
return total_sum
slow = fast = n
while True:
slow = get_sum_of_squares(slow)
fast = get_sum_of_squares(get_sum_of_squares(fast))
if fast == 1:
return True
if slow == fast:
return False
Practice Problems:
4. Merge Intervals
When to use: Problems involving overlapping intervals, scheduling, range merging.
Pattern Recognition:
Overlapping intervals
Meeting room problems
Insert intervals
Interval intersection
Template:
def merge_intervals(intervals):
if not intervals:
return []
8 / 60
[Link] 2025-07-02
return merged
[Link](newInterval)
return result
def min_meeting_rooms(intervals):
if not intervals:
return 0
start_pointer = end_pointer = 0
used_rooms = 0
used_rooms += 1
start_pointer += 1
return used_rooms
return result
Practice Problems:
5. Cyclic Sort
When to use: Problems with arrays containing numbers in a given range, missing numbers.
10 / 60
[Link] 2025-07-02
Pattern Recognition:
Template:
def cyclic_sort(nums):
i = 0
while i < len(nums):
correct_index = nums[i] - 1
if nums[i] != nums[correct_index]:
nums[i], nums[correct_index] = nums[correct_index], nums[i]
else:
i += 1
return nums
def find_missing_number(nums):
i = 0
n = len(nums)
# Cyclic sort
while i < n:
if nums[i] < n and nums[i] != nums[nums[i]]:
nums[nums[i]], nums[i] = nums[i], nums[nums[i]]
else:
i += 1
return n
def find_duplicates(nums):
i = 0
11 / 60
[Link] 2025-07-02
# Cyclic sort
while i < len(nums):
correct_index = nums[i] - 1
if nums[i] != nums[correct_index]:
nums[i], nums[correct_index] = nums[correct_index], nums[i]
else:
i += 1
# Find duplicates
duplicates = []
for i in range(len(nums)):
if nums[i] != i + 1:
[Link](nums[i])
return duplicates
def first_missing_positive(nums):
n = len(nums)
return n + 1
Practice Problems:
Pattern Recognition:
12 / 60
[Link] 2025-07-02
def reverse_list(head):
prev = None
current = head
while current:
next_temp = [Link]
[Link] = prev
prev = current
current = next_temp
return prev
return [Link]
13 / 60
[Link] 2025-07-02
current = head
while current and count < k:
current = [Link]
count += 1
head = current
return head
current = head
prev = None
while current:
last_node_prev_part = prev
last_node_sub_list = current
# Skip k nodes
for _ in range(k):
if not current:
break
next_temp = [Link]
[Link] = prev
prev = current
current = next_temp
if last_node_prev_part:
last_node_prev_part.next = prev
else:
head = prev
last_node_sub_list.next = current
# Skip k nodes
for _ in range(k):
14 / 60
[Link] 2025-07-02
if not current:
break
prev = current
current = [Link]
return head
Practice Problems:
Time Complexity: O(n) Space Complexity: O(w) where w is maximum width of tree
Pattern Recognition:
Level-order traversal
Finding minimum depth
Connecting nodes at same level
Zigzag traversal
Template:
def level_order(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = [Link]()
current_level.append([Link])
if [Link]:
[Link]([Link])
15 / 60
[Link] 2025-07-02
if [Link]:
[Link]([Link])
[Link](current_level)
return result
def right_side_view(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
for i in range(level_size):
node = [Link]()
if [Link]:
[Link]([Link])
if [Link]:
[Link]([Link])
return result
def zigzag_level_order(root):
if not root:
return []
result = []
queue = deque([root])
left_to_right = True
while queue:
level_size = len(queue)
current_level = deque()
for _ in range(level_size):
node = [Link]()
16 / 60
[Link] 2025-07-02
if left_to_right:
current_level.append([Link])
else:
current_level.appendleft([Link])
if [Link]:
[Link]([Link])
if [Link]:
[Link]([Link])
[Link](list(current_level))
left_to_right = not left_to_right
return result
def min_depth(root):
if not root:
return 0
while queue:
node, depth = [Link]()
if [Link]:
[Link](([Link], depth + 1))
if [Link]:
[Link](([Link], depth + 1))
return 0
Practice Problems:
17 / 60
[Link] 2025-07-02
Pattern Recognition:
Template (Recursive):
def dfs(root):
if not root:
return
Template (Iterative):
def dfs_iterative(root):
if not root:
return
stack = [root]
while stack:
node = [Link]()
18 / 60
[Link] 2025-07-02
# Leaf node
if not [Link] and not [Link]:
return [Link] == sum_target
def binary_tree_paths(root):
result = []
[Link](str([Link]))
# Leaf node
if not [Link] and not [Link]:
[Link]("->".join(path))
else:
dfs([Link], path)
dfs([Link], path)
[Link]() # Backtrack
dfs(root, [])
return result
def max_path_sum(root):
max_sum = float('-inf')
def max_gain(node):
nonlocal max_sum
if not node:
return 0
max_gain(root)
return max_sum
def diameter_of_binary_tree(root):
diameter = 0
def depth(node):
nonlocal diameter
if not node:
return 0
left_depth = depth([Link])
right_depth = depth([Link])
# Update diameter
diameter = max(diameter, left_depth + right_depth)
depth(root)
return diameter
Practice Problems:
9. Two Heaps
When to use: Finding median in data stream, sliding window median.
Time Complexity: O(log n) for insertion, O(1) for median Space Complexity: O(n)
Pattern Recognition:
Finding median
20 / 60
[Link] 2025-07-02
Template:
import heapq
class MedianFinder:
def __init__(self):
self.small_heap = [] # Max heap (negate values)
self.large_heap = [] # Min heap
# Balance heaps
if len(self.small_heap) > len(self.large_heap) + 1:
val = -[Link](self.small_heap)
[Link](self.large_heap, val)
elif len(self.large_heap) > len(self.small_heap) + 1:
val = [Link](self.large_heap)
[Link](self.small_heap, -val)
def find_median(self):
if len(self.small_heap) == len(self.large_heap):
return (-self.small_heap[0] + self.large_heap[0]) / 2.0
elif len(self.small_heap) > len(self.large_heap):
return -self.small_heap[0]
else:
return self.large_heap[0]
if k % 2 == 1:
[Link](float(window[k // 2]))
else:
[Link]((window[k // 2 - 1] + window[k // 2]) / 2.0)
return result
21 / 60
[Link] 2025-07-02
# Slide window
for i in range(k, len(nums)):
# Remove outgoing element
out_num = nums[i - k]
hash_table[out_num] += 1
return result
[Link](min_heap)
# Select up to k projects
for _ in range(k):
# Move all affordable projects to profit heap
while min_capital_heap and min_capital_heap[0][0] <= w:
cap, profit = [Link](min_capital_heap)
[Link](max_profit_heap, -profit)
return w
Practice Problems:
23 / 60
[Link] 2025-07-02
10. Subsets
When to use: Generating all combinations, permutations, or subsets.
Time Complexity: O(2^n) for subsets, O(n!) for permutations Space Complexity: O(n) for recursion depth
Pattern Recognition:
Subsets Template:
def subsets(nums):
result = []
backtrack(0, [])
return result
Iterative Subsets:
def subsets_iterative(nums):
result = [[]]
return result
24 / 60
[Link] 2025-07-02
def subsets_with_dup(nums):
[Link]() # Sort to handle duplicates
result = []
[Link](nums[i])
backtrack(i + 1, path)
[Link]()
backtrack(0, [])
return result
Example: Permutations
def permute(nums):
result = []
def backtrack(path):
if len(path) == len(nums):
[Link](path[:])
return
[Link](num)
backtrack(path)
[Link]()
backtrack([])
return result
def generate_parenthesis(n):
result = []
25 / 60
[Link] 2025-07-02
return
backtrack('', 0, 0)
return result
def letter_combinations(digits):
if not digits:
return []
phone_map = {
'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'
}
result = []
letters = phone_map[digits[index]]
for letter in letters:
backtrack(index + 1, path + letter)
backtrack(0, '')
return result
Practice Problems:
26 / 60
[Link] 2025-07-02
Pattern Recognition:
Template:
if arr[mid] == target:
return mid
return -1
if nums[mid] == target:
return 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
def find_min(nums):
left, right = 0, len(nums) - 1
return nums[left]
def find_peak_element(nums):
left, right = 0, len(nums) - 1
return left
28 / 60
[Link] 2025-07-02
if val == target:
return mid
elif val < target:
left = mid + 1
else:
right = mid - 1
return -1
if nums[mid] == target:
result = mid
right = mid - 1 # Continue searching left
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return result
def find_last():
left, right = 0, len(nums) - 1
result = -1
if nums[mid] == target:
result = mid
29 / 60
[Link] 2025-07-02
return result
Practice Problems:
Key Properties:
a⊕a=0
a⊕0=a
XOR is commutative and associative
Pattern Recognition:
Template:
def single_number(nums):
result = 0
for num in nums:
result ^= num
return result
30 / 60
[Link] 2025-07-02
def single_number_ii(nums):
ones = twos = 0
return ones
def single_number_iii(nums):
# XOR all numbers
xor_all = 0
for num in nums:
xor_all ^= num
def missing_number(nums):
n = len(nums)
result = n # Start with n
for i in range(n):
result ^= i ^ nums[i]
return result
31 / 60
[Link] 2025-07-02
def find_complement(num):
# Find bit length
bit_length = num.bit_length()
def flip_and_invert_image(A):
for row in A:
# Flip (reverse) and invert (XOR with 1)
for i in range((len(row) + 1) // 2):
j = len(row) - 1 - i
row[i], row[j] = row[j] ^ 1, row[i] ^ 1
return A
Practice Problems:
Time Complexity: O(n log k) with heap, O(n) with quickselect Space Complexity: O(k)
Pattern Recognition:
K largest/smallest elements
K most frequent elements
K closest points
32 / 60
[Link] 2025-07-02
import heapq
return list(heap)
count = Counter(nums)
33 / 60
[Link] 2025-07-02
heap = []
if k_smallest == pivot_index:
return nums[k_smallest]
elif k_smallest < pivot_index:
return quickselect(left, pivot_index - 1, k_smallest)
else:
return quickselect(pivot_index + 1, right, k_smallest)
34 / 60
[Link] 2025-07-02
heap = []
result = []
return result
Practice Problems:
Time Complexity: O(n log k) where n is total elements Space Complexity: O(k)
Pattern Recognition:
Template:
import heapq
def merge_k_sorted_arrays(arrays):
heap = []
result = []
35 / 60
[Link] 2025-07-02
while heap:
val, array_idx, element_idx = [Link](heap)
[Link](val)
return result
def merge_k_lists(lists):
import heapq
heap = []
# Initialize heap
for i, head in enumerate(lists):
if head:
[Link](heap, ([Link], i, head))
dummy = ListNode(0)
current = dummy
while heap:
val, i, node = [Link](heap)
[Link] = node
current = [Link]
if [Link]:
[Link](heap, ([Link], i, [Link]))
return [Link]
def smallest_range(nums):
import heapq
heap = []
max_val = float('-inf')
36 / 60
[Link] 2025-07-02
n = len(matrix)
heap = []
for _ in range(k):
val, row, col = [Link](heap)
if col + 1 < n:
[Link](heap, (matrix[row][col + 1], row, col + 1))
return val
Practice Problems:
37 / 60
[Link] 2025-07-02
Time Complexity: O(n × W) where W is knapsack capacity Space Complexity: O(n × W) or O(W) with
optimization
Pattern Recognition:
Basic Template:
return dp[n][capacity]
Space Optimized:
for i in range(len(weights)):
# Traverse backwards to avoid using updated values
for w in range(capacity, weights[i] - 1, -1):
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[capacity]
38 / 60
[Link] 2025-07-02
def can_partition(nums):
total_sum = sum(nums)
if total_sum % 2 != 0:
return False
target = total_sum // 2
dp = [False] * (target + 1)
dp[0] = True
return dp[target]
target = (S + total) // 2
dp = [0] * (target + 1)
dp[0] = 1
return dp[target]
for s in strs:
zeros = [Link]('0')
ones = [Link]('1')
# Traverse backwards
for i in range(m, zeros - 1, -1):
for j in range(n, ones - 1, -1):
dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1)
39 / 60
[Link] 2025-07-02
return dp[m][n]
Practice Problems:
Pattern Recognition:
Template:
return dp[capacity]
40 / 60
[Link] 2025-07-02
return dp[amount]
def num_squares(n):
dp = [float('inf')] * (n + 1)
dp[0] = 0
return dp[n]
return dp[target]
Practice Problems:
Pattern Recognition:
Template:
def fibonacci(n):
if n <= 1:
return n
prev2, prev1 = 0, 1
return prev1
def climb_stairs(n):
if n <= 2:
return n
prev2, prev1 = 1, 2
return prev1
def rob(nums):
if not nums:
return 0
if len(nums) == 1:
42 / 60
[Link] 2025-07-02
return nums[0]
return prev1
def rob_circular(nums):
if len(nums) == 1:
return nums[0]
def rob_linear(houses):
prev2, prev1 = 0, 0
for num in houses:
temp = max(prev1, prev2 + num)
prev2, prev1 = prev1, temp
return prev1
def num_decodings(s):
if not s or s[0] == '0':
return 0
prev2, prev1 = 1, 1
return prev1
Practice Problems:
43 / 60
[Link] 2025-07-02
Pattern Recognition:
Template:
def longest_palindromic_subsequence(s):
n = len(s)
dp = [[0] * n for _ in range(n)]
return dp[0][n-1]
def longest_palindromic_subsequence(s):
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
44 / 60
[Link] 2025-07-02
dp[i][j] = dp[i+1][j-1] + 2
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
return dp[0][n-1]
def min_deletions_palindrome(s):
n = len(s)
lps = longest_palindromic_subsequence(s)
return n - lps
def count_palindromic_subsequences(s):
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
return dp[0][n-1]
Practice Problems:
Pattern Recognition:
Template:
return dp[m][n]
return dp[m][n]
46 / 60
[Link] 2025-07-02
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
return dp[m][n]
return max_length
Practice Problems:
Pattern Recognition:
Course scheduling
Task ordering
Build dependencies
def topological_sort(graph):
in_degree = {node: 0 for node in graph}
for node in graph:
47 / 60
[Link] 2025-07-02
while queue:
node = [Link]()
[Link](node)
while queue:
course = [Link]()
completed += 1
in_degree[course] += 1
while queue:
course = [Link]()
[Link](course)
def alien_order(words):
graph = {}
in_degree = {}
# Initialize graph
for word in words:
for char in word:
if char not in graph:
graph[char] = []
in_degree[char] = 0
# Build graph
for i in range(len(words) - 1):
word1, word2 = words[i], words[i + 1]
min_len = min(len(word1), len(word2))
for j in range(min_len):
if word1[j] != word2[j]:
graph[word1[j]].append(word2[j])
in_degree[word2[j]] += 1
break
# Topological sort
queue = deque([char for char in in_degree if in_degree[char] == 0])
result = []
while queue:
char = [Link]()
[Link](char)
49 / 60
[Link] 2025-07-02
Practice Problems:
21. Trie
When to use: Prefix-based operations, word searches, autocomplete. Time Complexity: O(m) for
insert/search Space Complexity: O(ALPHABET_SIZE × N × M)
Pattern Recognition:
Template:
class TrieNode:
def __init__(self):
[Link] = {}
self.is_end = False
class Trie:
def __init__(self):
[Link] = TrieNode()
50 / 60
[Link] 2025-07-02
node = [Link][char]
return node.is_end
root = TrieNode()
# Build trie
for word in words:
node = root
for char in word:
if char not in [Link]:
[Link][char] = TrieNode()
node = [Link][char]
[Link] = word
node = [Link][char]
if [Link]:
[Link]([Link])
result = set()
for i in range(len(board)):
for j in range(len(board[0])):
dfs(i, j, root)
51 / 60
[Link] 2025-07-02
return list(result)
class WordDictionary:
def __init__(self):
[Link] = TrieNode()
char = word[i]
if char == '.':
for child in [Link]():
if dfs(child, i + 1):
return True
return False
else:
if char not in [Link]:
return False
return dfs([Link][char], i + 1)
return dfs([Link], 0)
Practice Problems:
Pattern Recognition:
52 / 60
[Link] 2025-07-02
Connected components
Cycle detection in undirected graphs
Minimum spanning tree
Template:
class UnionFind:
def __init__(self, n):
[Link] = list(range(n))
[Link] = [0] * n
[Link] = n
# Union by rank
if [Link][px] < [Link][py]:
px, py = py, px
[Link][py] = px
if [Link][px] == [Link][py]:
[Link][px] += 1
[Link] -= 1
return True
53 / 60
[Link] 2025-07-02
def find_redundant_connection(edges):
uf = UnionFind(len(edges) + 1)
for a, b in edges:
if not [Link](a, b):
return [a, b]
return []
def accounts_merge(accounts):
uf = UnionFind(len(accounts))
email_to_id = {}
groups = defaultdict(list)
for email, id in email_to_id.items():
groups[[Link](id)].append(email)
result = []
for id, emails in [Link]():
name = accounts[id][0]
[Link]([name] + sorted(emails))
return result
Practice Problems:
Pattern Recognition:
54 / 60
[Link] 2025-07-02
Temperature problems
Template:
def next_greater_element(nums):
stack = []
result = [-1] * len(nums)
for i in range(len(nums)):
while stack and nums[stack[-1]] < nums[i]:
result[[Link]()] = nums[i]
[Link](i)
return result
def daily_temperatures(temperatures):
stack = []
result = [0] * len(temperatures)
return result
def largest_rectangle_area(heights):
stack = []
max_area = 0
for i, h in enumerate(heights):
while stack and heights[stack[-1]] > h:
height = heights[[Link]()]
width = i if not stack else i - stack[-1] - 1
max_area = max(max_area, height * width)
[Link](i)
while stack:
height = heights[[Link]()]
width = len(heights) if not stack else len(heights) - stack[-1] - 1
max_area = max(max_area, height * width)
return max_area
55 / 60
[Link] 2025-07-02
def next_greater_elements(nums):
n = len(nums)
result = [-1] * n
stack = []
return result
Practice Problems:
24. Backtracking
When to use: Exploring all possible solutions, constraint satisfaction. Time Complexity: O(b^d) where b is
branching factor Space Complexity: O(d)
Pattern Recognition:
Template:
56 / 60
[Link] 2025-07-02
# Recurse
backtrack(result, current, update_remaining(remaining, choice))
# Backtrack
[Link]()
def generate_parenthesis(n):
result = []
if open_count < n:
backtrack(current + '(', open_count + 1, close_count)
backtrack('', 0, 0)
return result
Example: N-Queens
57 / 60
[Link] 2025-07-02
def solve_n_queens(n):
result = []
board = [['.' for _ in range(n)] for _ in range(n)]
# Check diagonals
for i, j in zip(range(row-1, -1, -1), range(col-1, -1, -1)):
if board[i][j] == 'Q':
return False
return True
def backtrack(row):
if row == n:
[Link]([''.join(row) for row in board])
return
backtrack(0)
return result
Practice Problems:
58 / 60
[Link] 2025-07-02
Common Pitfalls:
Next Steps:
59 / 60
[Link] 2025-07-02
Remember: Consistency beats intensity. Regular practice with these patterns will build your algorithmic
thinking and problem-solving skills over time.
60 / 60