Array Problems
Master array manipulation, sliding windows, and two-pointer techniques.
Two Sum
What You'll Learn
Hash maps for O(1) lookups and efficient pair finding.
Real-World Context
"Find two numbers in an array that add up to a target sum."
Breaking It Down
- Create a hash map to store numbers and their indices
- Iterate through the array
- For each number, check if target - current exists in map
- Return indices if found, otherwise add current to map
def twoSum(nums, target):
num_map = {}
for i, num in enumerate(nums):
complement = target - num
if complement in num_map:
return [num_map[complement], i]
num_map[num] = i
return []Line-by-Line Walkthrough
Using a hash map allows us to check for complements in constant time. This approach handles duplicates correctly and runs in linear time with linear space.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Single pass through array with hash map storage
Common Mistakes
- ❌ Using nested loops resulting in O(N²) time
✅ Use hash map for O(1) complement checks - ❌ Not handling duplicate values properly
✅ Store indices in map, not just presence
Try It Yourself
Three Sum - Find three numbers that sum to target
Best Time to Buy and Sell Stock
What You'll Learn
Single pass tracking of minimum and maximum profit.
Real-World Context
"Find the maximum profit from buying and selling a stock once."
Breaking It Down
- Initialize minimum price and maximum profit
- Iterate through prices
- Update minimum price if current is lower
- Update maximum profit if current profit is higher
- Return maximum profit
def maxProfit(prices):
if not prices:
return 0
min_price = float('inf')
max_profit = 0
for price in prices:
min_price = min(min_price, price)
max_profit = max(max_profit, price - min_price)
return max_profitLine-by-Line Walkthrough
By tracking the minimum price seen so far and calculating profit at each step, we find the optimal buy and sell points in one pass.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Single pass with constant extra space
Common Mistakes
- ❌ Trying all pairs of buy/sell dates
✅ Track minimum price and update profit continuously
Try It Yourself
Best Time to Buy and Sell Stock II - Multiple transactions allowed
Contains Duplicate
What You'll Learn
Using hash sets for efficient duplicate detection.
Real-World Context
"Determine if an array contains any duplicate values."
Breaking It Down
- Create a hash set to store seen numbers
- Iterate through the array
- If number is already in set, return true
- Otherwise add to set
- Return false if no duplicates found
def containsDuplicate(nums):
seen = set()
for num in nums:
if num in seen:
return True
seen.add(num)
return FalseLine-by-Line Walkthrough
Hash sets provide O(1) average time complexity for insertions and lookups, making this approach efficient for large arrays.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Single pass with hash set storage
Common Mistakes
- ❌ Sorting the array and checking adjacent elements
✅ Use hash set for O(N) time instead of O(N log N)
Try It Yourself
Contains Duplicate II - Check for duplicates within k distance
Product of Array Except Self
What You'll Learn
Prefix and suffix products to avoid division and handle zeros.
Real-World Context
"Return an array where each element is the product of all elements except itself."
Breaking It Down
- Create result array initialized to 1
- Compute prefix products from left to right
- Compute suffix products from right to left
- Multiply prefix and suffix for each position
- Handle zeros by tracking zero count
def productExceptSelf(nums):
n = len(nums)
result = [1] * n
prefix = 1
for i in range(n):
result[i] = prefix
prefix *= nums[i]
suffix = 1
for i in range(n-1, -1, -1):
result[i] *= suffix
suffix *= nums[i]
return resultLine-by-Line Walkthrough
By computing prefix and suffix products separately, we avoid using division and handle zeros correctly. This approach runs in linear time with constant extra space (excluding output).
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Two passes with output array as extra space
Common Mistakes
- ❌ Using division by total product
✅ Use prefix/suffix products to handle zeros
Try It Yourself
Maximum Product Subarray - Find subarray with maximum product
Maximum Subarray
What You'll Learn
Kadane's algorithm for maximum subarray sum.
Real-World Context
"Find the contiguous subarray with the largest sum."
Breaking It Down
- Initialize current sum and maximum sum to first element
- Iterate through the array from second element
- Update current sum: max(current + num, num)
- Update maximum sum if current is larger
- Return maximum sum
def maxSubArray(nums):
if not nums:
return 0
max_current = max_global = nums[0]
for num in nums[1:]:
max_current = max(num, max_current + num)
if max_current > max_global:
max_global = max_current
return max_globalLine-by-Line Walkthrough
Kadane's algorithm keeps track of the maximum sum ending at each position. When the current sum becomes negative, we start a new subarray.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Single pass with constant space
Common Mistakes
- ❌ Using brute force O(N²) approach
✅ Use Kadane's algorithm for O(N) time
Try It Yourself
Maximum Product Subarray - Similar but for product
Maximum Product Subarray
What You'll Learn
Tracking both maximum and minimum products for negative numbers.
Real-World Context
"Find the contiguous subarray with the largest product."
Breaking It Down
- Initialize max_so_far, min_so_far, and result to first element
- Iterate through the array
- If current is negative, swap max and min
- Update max_so_far and min_so_far
- Update result with max_so_far
- Return result
def maxProduct(nums):
if not nums:
return 0
max_so_far = min_so_far = result = nums[0]
for num in nums[1:]:
if num < 0:
max_so_far, min_so_far = min_so_far, max_so_far
max_so_far = max(num, max_so_far * num)
min_so_far = min(num, min_so_far * num)
result = max(result, max_so_far)
return resultLine-by-Line Walkthrough
Because of negative numbers, we need to track both the maximum and minimum product ending at each position. Negative numbers can turn a minimum into a maximum when multiplied.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Single pass with constant space
Common Mistakes
- ❌ Not handling negative numbers properly
✅ Track both max and min products
Try It Yourself
Maximum Subarray - Sum instead of product
Find Minimum in Rotated Sorted Array
What You'll Learn
Binary search in rotated arrays.
Real-World Context
"Find the minimum element in a rotated sorted array."
Breaking It Down
- Use binary search with left and right pointers
- If array is not rotated, return first element
- Check middle element
- If mid > right, minimum is in right half
- If mid < right, minimum is in left half
- Return the minimum found
def findMin(nums):
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] > nums[right]:
left = mid + 1
else:
right = mid
return nums[left]Line-by-Line Walkthrough
The rotation point is where the minimum is. We use binary search to find where the array is no longer sorted.
Complexity Analysis
- Time: O(log N)
- Space: O(1)
- Binary search with constant space
Common Mistakes
- ❌ Linear search
✅ Use binary search for log N time
Try It Yourself
Search in Rotated Sorted Array - Find specific element
Search in Rotated Sorted Array
What You'll Learn
Modified binary search for rotated arrays.
Real-World Context
"Search for a target in a rotated sorted array."
Breaking It Down
- Use binary search
- Determine which half is sorted
- Check if target is in the sorted half
- Adjust pointers accordingly
- Return index or -1 if not found
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1Line-by-Line Walkthrough
We determine which half is sorted and check if the target could be in that half, then search accordingly.
Complexity Analysis
- Time: O(log N)
- Space: O(1)
- Modified binary search
Common Mistakes
- ❌ Not identifying the sorted half
✅ Check nums[left] <= nums[mid] to determine sorted half
Try It Yourself
Find Minimum in Rotated Sorted Array - Find minimum instead of search
3Sum
What You'll Learn
Two pointers with sorting for efficient triplet finding.
Real-World Context
"Find all unique triplets that sum to zero."
Breaking It Down
- Sort the array
- For each element, use two pointers on the rest
- Skip duplicates to avoid duplicate triplets
- Move pointers based on sum comparison
- Add valid triplets to result
def threeSum(nums):
nums.sort()
result = []
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i-1]:
continue
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
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
return resultLine-by-Line Walkthrough
Sorting allows us to use two pointers efficiently. We fix one element and find pairs that sum to its negative.
Complexity Analysis
- Time: O(N²)
- Space: O(1)
- Sorting is O(N log N), two pointers is O(N²)
Common Mistakes
- ❌ Not skipping duplicates
✅ Skip duplicate elements to avoid duplicate triplets
Try It Yourself
4Sum - Find four numbers that sum to target
Container With Most Water
What You'll Learn
Two pointers for maximum area calculation.
Real-World Context
"Find two lines that form a container with the most water."
Breaking It Down
- Use two pointers at start and end
- Calculate area as min(height[left], height[right]) * (right - left)
- Move the pointer with smaller height inward
- Track maximum area
- Return maximum area
def maxArea(height):
left, right = 0, len(height) - 1
max_area = 0
while left < right:
width = right - left
h = min(height[left], height[right])
area = width * h
max_area = max(max_area, area)
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_areaLine-by-Line Walkthrough
Moving the shorter line inward gives a chance for a taller line, potentially increasing the area.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Single pass with two pointers
Common Mistakes
- ❌ Brute force all pairs
✅ Use two pointers to achieve O(N) time
Try It Yourself
Trapping Rain Water - Calculate trapped water
Binary Problems
Bit manipulation, bitwise operations, and binary representations.
Sum of Two Integers
What You'll Learn
Bit manipulation for addition without + or -.
Real-World Context
"Calculate sum of two integers using bitwise operations."
Breaking It Down
- Use XOR for sum without carry
- Use AND and left shift for carry
- Repeat until no carry
- Return the result
def getSum(a, b):
while b != 0:
carry = a & b
a = a ^ b
b = carry << 1
return aLine-by-Line Walkthrough
This simulates binary addition: XOR gives sum bits, AND gives carry bits.
Complexity Analysis
- Time: O(1)
- Space: O(1)
- Fixed number of bits
Common Mistakes
- ❌ Using + operator
✅ Use bitwise operations
Try It Yourself
Subtract two numbers using bitwise operations
Number of 1 Bits
What You'll Learn
Bit manipulation and counting set bits efficiently.
Real-World Context
"Count the number of 1 bits in the binary representation of a number."
Breaking It Down
- Initialize counter to 0
- While number is not zero
- Check if least significant bit is 1 using n & 1
- Add to counter and right shift n by 1
- Return counter
def hammingWeight(n):
count = 0
while n:
count += n & 1
n >>= 1
return countLine-by-Line Walkthrough
By checking each bit individually through right shifts, we count all set bits. This works for both positive and unsigned integers.
Complexity Analysis
- Time: O(1)
- Space: O(1)
- Fixed 32-bit operations regardless of input
Common Mistakes
- ❌ Using string conversion and counting '1's
✅ Use bitwise operations for efficiency
Try It Yourself
Counting Bits - Count bits for all numbers up to n
Counting Bits
What You'll Learn
Dynamic programming for bit counting.
Real-World Context
"Count the number of 1 bits for all numbers from 0 to n."
Breaking It Down
- Initialize dp array of size n+1
- For each number i from 1 to n
- dp[i] = dp[i >> 1] + (i & 1)
- Return the dp array
def countBits(n):
dp = [0] * (n + 1)
for i in range(1, n + 1):
dp[i] = dp[i >> 1] + (i & 1)
return dpLine-by-Line Walkthrough
Each number's bit count is the bit count of its half plus the least significant bit.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Linear pass building dp array
Common Mistakes
- ❌ Counting bits for each number separately
✅ Use DP to reuse previous calculations
Try It Yourself
Number of 1 Bits - Count for single number
Missing Number
What You'll Learn
XOR for finding missing element in sequence.
Real-World Context
"Find the missing number in array of 0 to n."
Breaking It Down
- XOR all numbers from 0 to n
- XOR all numbers in the array
- XOR the two results to find missing number
- Return the missing number
def missingNumber(nums):
n = len(nums)
xor_all = 0
for i in range(n + 1):
xor_all ^= i
for num in nums:
xor_all ^= num
return xor_allLine-by-Line Walkthrough
XOR of a number with itself is 0, so all present numbers cancel out, leaving the missing one.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Single pass with XOR
Common Mistakes
- ❌ Using sum and subtracting
✅ Use XOR to avoid overflow issues
Try It Yourself
Find the duplicate number - Similar but with duplicate
Reverse Bits
What You'll Learn
Bit manipulation for reversing bit order.
Real-World Context
"Reverse the bits of a 32-bit unsigned integer."
Breaking It Down
- Initialize result to 0
- For 32 bits
- Shift result left by 1
- Add least significant bit of n to result
- Shift n right by 1
- Return result
def reverseBits(n):
result = 0
for _ in range(32):
result = (result << 1) | (n & 1)
n >>= 1
return resultLine-by-Line Walkthrough
We build the reversed number bit by bit, taking the LSB of n and putting it as MSB of result.
Complexity Analysis
- Time: O(1)
- Space: O(1)
- Fixed 32 operations
Common Mistakes
- ❌ Not handling 32 bits properly
✅ Loop exactly 32 times
Try It Yourself
Number of 1 Bits - Count set bits instead of reverse
Dynamic Programming
Breaking problems into subproblems and optimal substructure.
Climbing Stairs
What You'll Learn
Bottom-up DP for counting ways with recurrence relations.
Real-World Context
"Find the number of ways to climb n stairs, taking 1 or 2 steps at a time."
Breaking It Down
- Handle base cases: 0 stairs = 1 way, 1 stair = 1 way
- Initialize dp array or variables for previous two steps
- For each step from 2 to n, ways = ways(n-1) + ways(n-2)
- Return the nth step ways
def climbStairs(n):
if n <= 1:
return 1
prev1, prev2 = 1, 1
for i in range(2, n + 1):
current = prev1 + prev2
prev2 = prev1
prev1 = current
return prev1Line-by-Line Walkthrough
This is the Fibonacci sequence where each step depends on the previous two. Using iterative DP avoids recursion stack overflow.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Linear pass with constant space using two variables
Common Mistakes
- ❌ Using full recursion without memoization
✅ Use iterative approach or memoization to avoid exponential time
Try It Yourself
House Robber - Maximize robbery with adjacent house constraint
Coin Change
What You'll Learn
DP for minimum coins to make amount.
Real-World Context
"Find the minimum number of coins needed to make a given amount."
Breaking It Down
- Initialize dp array with infinity, dp[0] = 0
- For each coin, for each amount from coin to target
- dp[amount] = min(dp[amount], dp[amount - coin] + 1)
- Return dp[target] if not infinity, else -1
def coinChange(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for i in range(coin, amount + 1):
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1Line-by-Line Walkthrough
We build up the minimum coins for each amount using previously computed values.
Complexity Analysis
- Time: O(amount * coins)
- Space: O(amount)
- Nested loops over coins and amounts
Common Mistakes
- ❌ Greedy approach without checking all possibilities
✅ Use DP to consider all combinations
Try It Yourself
Coin Change 2 - Number of ways to make amount
Longest Increasing Subsequence
What You'll Learn
DP for longest increasing subsequence.
Real-World Context
"Find the length of the longest increasing subsequence."
Breaking It Down
- Initialize dp array with 1s
- For each element, check all previous elements
- If nums[i] > nums[j], dp[i] = max(dp[i], dp[j] + 1)
- Return the maximum in dp
def lengthOfLIS(nums):
if not nums:
return 0
dp = [1] * len(nums)
for i in range(1, len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)Line-by-Line Walkthrough
For each element, we look at all previous elements and extend the longest subsequence ending before it.
Complexity Analysis
- Time: O(N²)
- Space: O(N)
- Nested loops for each pair
Common Mistakes
- ❌ Not considering all previous elements
✅ Check every previous element for extension
Try It Yourself
Longest Common Subsequence - Between two strings
Longest Common Subsequence
What You'll Learn
2D DP for string comparison.
Real-World Context
"Find the length of the longest common subsequence between two strings."
Breaking It Down
- Create 2D dp table of size (m+1) x (n+1)
- If characters match, dp[i][j] = dp[i-1][j-1] + 1
- Else, dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- Return dp[m][n]
def longestCommonSubsequence(text1, text2):
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]Line-by-Line Walkthrough
The DP table represents the LCS length for substrings. We build it bottom-up.
Complexity Analysis
- Time: O(M*N)
- Space: O(M*N)
- 2D table for both strings
Common Mistakes
- ❌ Not using DP, trying recursive approach
✅ Use 2D DP for efficiency
Try It Yourself
Edit Distance - Minimum operations to transform one string to another
Word Break
What You'll Learn
DP for string segmentation.
Real-World Context
"Determine if a string can be segmented into words from a dictionary."
Breaking It Down
- Initialize dp array of size len(s) + 1, dp[0] = True
- For each position i, if dp[i] is True
- Check substrings from i to j
- If substring in wordDict, set dp[j] = True
- Return dp[len(s)]
def wordBreak(s, wordDict):
wordSet = set(wordDict)
dp = [False] * (len(s) + 1)
dp[0] = True
for i in range(len(s) + 1):
if dp[i]:
for j in range(i + 1, len(s) + 1):
if s[i:j] in wordSet:
dp[j] = True
return dp[len(s)]Line-by-Line Walkthrough
We use DP to mark positions where we can reach with dictionary words.
Complexity Analysis
- Time: O(N²)
- Space: O(N)
- Nested loops over string positions
Common Mistakes
- ❌ Not using DP, trying all combinations
✅ Use DP to avoid redundant checks
Try It Yourself
Word Break II - Return all possible sentences
Combination Sum
What You'll Learn
Backtracking for combination problems.
Real-World Context
"Find all unique combinations that sum to target."
Breaking It Down
- Sort the candidates
- Use backtracking function
- For each candidate, add to current combination
- Recurse with remaining target
- Backtrack by removing the candidate
- Skip duplicates
def combinationSum(candidates, target):
def backtrack(start, target, path):
if target == 0:
result.append(path[:])
return
for i in range(start, len(candidates)):
if candidates[i] > target:
break
path.append(candidates[i])
backtrack(i, target - candidates[i], path)
path.pop()
candidates.sort()
result = []
backtrack(0, target, [])
return resultLine-by-Line Walkthrough
Backtracking explores all possible combinations, pruning when sum exceeds target.
Complexity Analysis
- Time: O(2^N)
- Space: O(N)
- Exponential in worst case, but pruning helps
Common Mistakes
- ❌ Not sorting and skipping duplicates
✅ Sort candidates and start from current index
Try It Yourself
Combination Sum II - Each number used at most once
House Robber
What You'll Learn
DP for maximum sum with constraints.
Real-World Context
"Maximize the amount of money robbed with no adjacent houses."
Breaking It Down
- Initialize dp[0] = nums[0], dp[1] = max(nums[0], nums[1])
- For i from 2 to n-1, dp[i] = max(dp[i-1], dp[i-2] + nums[i])
- Return dp[n-1]
def rob(nums):
if not nums:
return 0
if len(nums) == 1:
return nums[0]
dp = [0] * len(nums)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
return dp[-1]Line-by-Line Walkthrough
We choose for each house whether to rob it or not, based on previous decisions.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Linear pass with dp array
Common Mistakes
- ❌ Not considering the constraint
✅ Use DP to track maximum at each step
Try It Yourself
House Robber II - Circular houses
House Robber II
What You'll Learn
DP with circular constraint.
Real-World Context
"Maximize robbery in a circle of houses."
Breaking It Down
- Handle edge cases
- Compute max for houses 0 to n-2
- Compute max for houses 1 to n-1
- Return the maximum of the two
def rob(nums):
def rob_linear(nums):
if not nums:
return 0
prev1, prev2 = 0, 0
for num in nums:
current = max(prev1, prev2 + num)
prev2 = prev1
prev1 = current
return prev1
if len(nums) == 1:
return nums[0]
return max(rob_linear(nums[:-1]), rob_linear(nums[1:]))Line-by-Line Walkthrough
Since it's circular, we can't rob both first and last. So we compute max excluding first and excluding last.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Two linear passes
Common Mistakes
- ❌ Not handling the circular constraint
✅ Compute max for two separate ranges
Try It Yourself
House Robber - Linear houses
Decode Ways
What You'll Learn
DP for counting ways to decode string.
Real-World Context
"Number of ways to decode a string of digits."
Breaking It Down
- Initialize dp[0] = 1, dp[1] = 1 if s[0] != '0'
- For i from 2 to len(s)
- If s[i-1] != '0', dp[i] += dp[i-1]
- If s[i-2:i] is valid two-digit, dp[i] += dp[i-2]
- Return dp[len(s)]
def numDecodings(s):
if not s or s[0] == '0':
return 0
dp = [0] * (len(s) + 1)
dp[0] = 1
dp[1] = 1
for i in range(2, len(s) + 1):
if s[i-1] != '0':
dp[i] += dp[i-1]
two_digit = int(s[i-2:i])
if 10 <= two_digit <= 26:
dp[i] += dp[i-2]
return dp[len(s)]Line-by-Line Walkthrough
Each position can be reached from previous one or two positions if valid.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Linear pass with dp array
Common Mistakes
- ❌ Not checking for leading zeros
✅ Handle '0' cases carefully
Try It Yourself
Decode Ways II - With '*' wildcards
Unique Paths
What You'll Learn
2D DP for grid path counting.
Real-World Context
"Number of unique paths from top-left to bottom-right in grid."
Breaking It Down
- Initialize dp grid with 1s for first row and column
- For each cell, dp[i][j] = dp[i-1][j] + dp[i][j-1]
- Return dp[m-1][n-1]
def uniquePaths(m, n):
dp = [[1] * n for _ in range(m)]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]Line-by-Line Walkthrough
Each cell's paths come from top and left cells.
Complexity Analysis
- Time: O(M*N)
- Space: O(M*N)
- Fill the grid
Common Mistakes
- ❌ Using recursion without memoization
✅ Use DP to avoid recomputation
Try It Yourself
Unique Paths II - With obstacles
Jump Game
What You'll Learn
Greedy for jump reachability.
Real-World Context
"Determine if you can reach the last index."
Breaking It Down
- Initialize farthest = 0
- For each index i up to farthest
- Update farthest = max(farthest, i + nums[i])
- If farthest >= len-1, return true
- Return false
def canJump(nums):
farthest = 0
for i in range(len(nums)):
if i > farthest:
return False
farthest = max(farthest, i + nums[i])
if farthest >= len(nums) - 1:
return True
return TrueLine-by-Line Walkthrough
We keep track of the farthest we can reach so far.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Single pass
Common Mistakes
- ❌ Using DP for each position
✅ Use greedy to track maximum reach
Try It Yourself
Jump Game II - Minimum jumps to reach end
Graph Problems
Graph traversal, cycles, and connectivity.
Clone Graph
What You'll Learn
DFS or BFS for graph cloning.
Real-World Context
"Create a deep copy of a connected undirected graph."
Breaking It Down
- Use DFS or BFS
- Maintain a map from original to cloned nodes
- For each neighbor, clone if not already cloned
- Return the cloned start node
def cloneGraph(node):
if not node:
return None
cloned = {}
def dfs(node):
if node in cloned:
return cloned[node]
clone = Node(node.val)
cloned[node] = clone
for neighbor in node.neighbors:
clone.neighbors.append(dfs(neighbor))
return clone
return dfs(node)Line-by-Line Walkthrough
We use a map to keep track of cloned nodes to avoid infinite recursion.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Visit each node and edge once
Common Mistakes
- ❌ Not using a map, causing infinite loops
✅ Use map to track cloned nodes
Try It Yourself
Copy List with Random Pointer - Similar with random pointers
Course Schedule
What You'll Learn
Topological sort with DFS or Kahn's algorithm.
Real-World Context
"Determine if you can finish all courses given prerequisites."
Breaking It Down
- Build adjacency list and indegree array
- Use queue for Kahn's algorithm
- Add nodes with indegree 0 to queue
- Process queue, reduce indegrees
- If all nodes processed, no cycle
def canFinish(numCourses, prerequisites):
from collections import deque
adj = [[] for _ in range(numCourses)]
indegree = [0] * numCourses
for course, prereq in prerequisites:
adj[prereq].append(course)
indegree[course] += 1
queue = deque([i for i in range(numCourses) if indegree[i] == 0])
count = 0
while queue:
course = queue.popleft()
count += 1
for neighbor in adj[course]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
queue.append(neighbor)
return count == numCoursesLine-by-Line Walkthrough
If there's a cycle, not all courses can be finished. Topological sort detects this.
Complexity Analysis
- Time: O(V+E)
- Space: O(V+E)
- Adjacency list and queue
Common Mistakes
- ❌ Not detecting cycles
✅ Use topological sort
Try It Yourself
Course Schedule II - Return the order
Pacific Atlantic Water Flow
What You'll Learn
Multi-source BFS or DFS for flow problems.
Real-World Context
"Find cells that can flow to both oceans."
Breaking It Down
- Use DFS or BFS from Pacific and Atlantic borders
- Mark cells reachable to each ocean
- Find intersection of both sets
def pacificAtlantic(matrix):
if not matrix or not matrix[0]:
return []
m, n = len(matrix), len(matrix[0])
pacific = set()
atlantic = set()
def dfs(i, j, visited, prev_height):
if (i, j) in visited or i < 0 or i >= m or j < 0 or j >= n or matrix[i][j] < prev_height:
return
visited.add((i, j))
dfs(i+1, j, visited, matrix[i][j])
dfs(i-1, j, visited, matrix[i][j])
dfs(i, j+1, visited, matrix[i][j])
dfs(i, j-1, visited, matrix[i][j])
for i in range(m):
dfs(i, 0, pacific, matrix[i][0])
dfs(i, n-1, atlantic, matrix[i][n-1])
for j in range(n):
dfs(0, j, pacific, matrix[0][j])
dfs(m-1, j, atlantic, matrix[m-1][j])
return list(pacific & atlantic)Line-by-Line Walkthrough
We start from the borders and flow to higher or equal heights.
Complexity Analysis
- Time: O(M*N)
- Space: O(M*N)
- Visit each cell multiple times
Common Mistakes
- ❌ Not starting from borders
✅ Multi-source from ocean borders
Try It Yourself
Surrounded Regions - Mark regions not surrounded
Number of Islands
What You'll Learn
DFS or BFS for connected components.
Real-World Context
"Count the number of islands in a grid."
Breaking It Down
- Iterate through grid
- When find '1', increment count and DFS/BFS to mark visited
- Mark all connected '1's as visited
- Return count
def numIslands(grid):
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
count = 0
def dfs(i, j):
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] == '0':
return
grid[i][j] = '0'
dfs(i+1, j)
dfs(i-1, j)
dfs(i, j+1)
dfs(i, j-1)
for i in range(m):
for j in range(n):
if grid[i][j] == '1':
count += 1
dfs(i, j)
return countLine-by-Line Walkthrough
Each DFS/BFS marks one island and all its connected land.
Complexity Analysis
- Time: O(M*N)
- Space: O(M*N)
- Visit each cell once
Common Mistakes
- ❌ Not marking visited
✅ Mark cells as visited during traversal
Try It Yourself
Max Area of Island - Find largest island area
Longest Consecutive Sequence
What You'll Learn
Hash set for efficient lookups.
Real-World Context
"Find the longest consecutive sequence in array."
Breaking It Down
- Put all numbers in a hash set
- For each number, if it's the start of a sequence
- Count consecutive numbers
- Update max length
def longestConsecutive(nums):
num_set = set(nums)
max_length = 0
for num in nums:
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_lengthLine-by-Line Walkthrough
We only start counting from numbers that are the beginning of sequences.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Hash set operations
Common Mistakes
- ❌ Sorting the array
✅ Use hash set for O(1) lookups
Try It Yourself
Consecutive Numbers Sum - Number of ways to write as sum of consecutives
Alien Dictionary
What You'll Learn
Topological sort for ordering.
Real-World Context
"Derive the order of letters from alien dictionary."
Breaking It Down
- Build graph from adjacent words
- Use topological sort
- Return the order or detect cycle
def alienOrder(words):
from collections import defaultdict, deque
adj = defaultdict(list)
indegree = {c: 0 for word in words for c in word}
for i in range(len(words) - 1):
w1, w2 = words[i], words[i+1]
for j in range(min(len(w1), len(w2))):
if w1[j] != w2[j]:
if w2[j] not in adj[w1[j]]:
adj[w1[j]].append(w2[j])
indegree[w2[j]] += 1
break
else:
if len(w1) > len(w2):
return ""
queue = deque([c for c in indegree if indegree[c] == 0])
result = []
while queue:
c = queue.popleft()
result.append(c)
for neighbor in adj[c]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
queue.append(neighbor)
return "".join(result) if len(result) == len(indegree) else ""Line-by-Line Walkthrough
We build edges between letters that appear in order in adjacent words.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Limited alphabet size
Common Mistakes
- ❌ Not handling invalid cases
✅ Check for cycles and invalid orders
Try It Yourself
Course Schedule - Similar topological sort
Interval Problems
Sorting and merging intervals.
Insert Interval
What You'll Learn
Insert and merge intervals.
Real-World Context
"Insert a new interval into sorted intervals and merge overlapping."
Breaking It Down
- Find position to insert
- Insert the interval
- Merge overlapping intervals
def insert(intervals, newInterval):
result = []
i = 0
while i < len(intervals) and intervals[i][1] < newInterval[0]:
result.append(intervals[i])
i += 1
while i < len(intervals) and intervals[i][0] <= newInterval[1]:
newInterval[0] = min(newInterval[0], intervals[i][0])
newInterval[1] = max(newInterval[1], intervals[i][1])
i += 1
result.append(newInterval)
while i < len(intervals):
result.append(intervals[i])
i += 1
return resultLine-by-Line Walkthrough
We insert the new interval and merge with overlapping ones.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Single pass through intervals
Common Mistakes
- ❌ Not merging properly
✅ Update the new interval bounds
Try It Yourself
Merge Intervals - Merge all overlapping
Merge Intervals
What You'll Learn
Sort and merge overlapping intervals.
Real-World Context
"Merge all overlapping intervals."
Breaking It Down
- Sort intervals by start time
- Initialize result with first interval
- For each interval, if overlaps with last in result, merge
- Else append to result
def merge(intervals):
if not intervals:
return []
intervals.sort(key=lambda x: x[0])
result = [intervals[0]]
for interval in intervals[1:]:
if interval[0] <= result[-1][1]:
result[-1][1] = max(result[-1][1], interval[1])
else:
result.append(interval)
return resultLine-by-Line Walkthrough
Sorting allows us to merge adjacent overlapping intervals.
Complexity Analysis
- Time: O(N log N)
- Space: O(N)
- Sorting dominates
Common Mistakes
- ❌ Not sorting first
✅ Sort by start time
Try It Yourself
Insert Interval - Insert one interval
Non-overlapping Intervals
What You'll Learn
Greedy for minimum removals.
Real-World Context
"Find minimum number of intervals to remove to make non-overlapping."
Breaking It Down
- Sort by end time
- Initialize count and end
- For each interval, if overlaps, increment count
- Else update end
def eraseOverlapIntervals(intervals):
if not intervals:
return 0
intervals.sort(key=lambda x: x[1])
count = 0
end = intervals[0][1]
for interval in intervals[1:]:
if interval[0] < end:
count += 1
else:
end = interval[1]
return countLine-by-Line Walkthrough
By sorting by end time, we greedily keep intervals that end earliest.
Complexity Analysis
- Time: O(N log N)
- Space: O(1)
- Sorting
Common Mistakes
- ❌ Sorting by start time
✅ Sort by end time for greedy choice
Try It Yourself
Meeting Rooms - Check if can attend all meetings
Meeting Rooms II
What You'll Learn
Priority queue for minimum rooms.
Real-World Context
"Find minimum number of meeting rooms required."
Breaking It Down
- Sort meetings by start time
- Use min-heap for end times
- For each meeting, if heap top <= start, pop
- Push current end, update max rooms
def minMeetingRooms(intervals):
if not intervals:
return 0
intervals.sort(key=lambda x: x[0])
import heapq
heap = []
max_rooms = 0
for interval in intervals:
while heap and heap[0] <= interval[0]:
heapq.heappop(heap)
heapq.heappush(heap, interval[1])
max_rooms = max(max_rooms, len(heap))
return max_roomsLine-by-Line Walkthrough
The heap keeps track of the earliest ending meeting in each room.
Complexity Analysis
- Time: O(N log N)
- Space: O(N)
- Sorting and heap operations
Common Mistakes
- ❌ Not using heap
✅ Use min-heap for end times
Try It Yourself
Meeting Rooms - Check if one room suffices
Linked List Problems
Pointer manipulation and traversal.
Reverse a Linked List
What You'll Learn
Iterative and recursive reversal.
Real-World Context
"Reverse a singly linked list."
Breaking It Down
- Initialize prev = None, current = head
- While current, next = current.next
- current.next = prev
- prev = current, current = next
- Return prev
def reverseList(head):
prev = None
current = head
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
return prevLine-by-Line Walkthrough
We reverse the links one by one, keeping track of previous node.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Single pass
Common Mistakes
- ❌ Losing the next pointer
✅ Store next before changing links
Try It Yourself
Reverse Linked List II - Reverse portion of list
Detect Cycle in a Linked List
What You'll Learn
Floyd's cycle detection algorithm.
Real-World Context
"Determine if linked list has a cycle."
Breaking It Down
- Use slow and fast pointers
- Slow moves one step, fast two steps
- If they meet, cycle exists
- Return true if meet, false if fast reaches end
def hasCycle(head):
if not head or not head.next:
return False
slow = head
fast = head.next
while fast and fast.next:
if slow == fast:
return True
slow = slow.next
fast = fast.next.next
return FalseLine-by-Line Walkthrough
If there's a cycle, fast will eventually catch up to slow.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Linear traversal
Common Mistakes
- ❌ Using hash set
✅ Use two pointers for O(1) space
Try It Yourself
Linked List Cycle II - Find cycle start
Merge Two Sorted Lists
What You'll Learn
Merging two sorted lists.
Real-World Context
"Merge two sorted linked lists."
Breaking It Down
- Use dummy node
- Compare heads, append smaller
- Move pointers
- Append remaining list
def mergeTwoLists(list1, list2):
dummy = ListNode()
current = dummy
while list1 and list2:
if list1.val < list2.val:
current.next = list1
list1 = list1.next
else:
current.next = list2
list2 = list2.next
current = current.next
current.next = list1 if list1 else list2
return dummy.nextLine-by-Line Walkthrough
We build the merged list by always taking the smaller head.
Complexity Analysis
- Time: O(N + M)
- Space: O(1)
- Single pass through both lists
Common Mistakes
- ❌ Not handling remaining nodes
✅ Append the remaining list at the end
Try It Yourself
Merge K Sorted Lists - Merge multiple lists
Merge K Sorted Lists
What You'll Learn
Priority queue for merging k lists.
Real-World Context
"Merge k sorted linked lists."
Breaking It Down
- Use min-heap with first node of each list
- While heap not empty, pop smallest
- Add its next to heap
- Append to result
def mergeKLists(lists):
import heapq
heap = []
for i, lst in enumerate(lists):
if lst:
heapq.heappush(heap, (lst.val, i, lst))
dummy = ListNode()
current = dummy
while heap:
val, i, node = heapq.heappop(heap)
current.next = node
current = current.next
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.nextLine-by-Line Walkthrough
The heap always gives us the smallest current node.
Complexity Analysis
- Time: O(N log K)
- Space: O(K)
- Heap of size K
Common Mistakes
- ❌ Merging pairwise
✅ Use heap for efficient merging
Try It Yourself
Merge Two Sorted Lists - For two lists
Remove Nth Node From End Of List
What You'll Learn
Two pointers for nth from end.
Real-World Context
"Remove the nth node from the end of linked list."
Breaking It Down
- Use dummy node
- Two pointers, fast moves n+1 steps ahead
- Then move both until fast reaches end
- Remove the node at slow.next
def removeNthFromEnd(head, n):
dummy = ListNode(0, head)
fast = dummy
for _ in range(n + 1):
fast = fast.next
slow = dummy
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.nextLine-by-Line Walkthrough
The fast pointer creates the gap of n nodes between slow and fast.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Single pass
Common Mistakes
- ❌ Counting from beginning
✅ Use two pointers
Try It Yourself
Remove Duplicates from Sorted List - Remove duplicates
Reorder List
What You'll Learn
Finding middle and reversing second half.
Real-World Context
"Reorder list to L0->Ln->L1->Ln-1->..."
Breaking It Down
- Find middle with slow/fast
- Reverse second half
- Merge the two halves alternately
def reorderList(head):
if not head or not head.next:
return
# Find middle
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
prev = None
current = slow
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
# Merge
first = head
second = prev
while second.next:
temp1 = first.next
temp2 = second.next
first.next = second
second.next = temp1
first = temp1
second = temp2Line-by-Line Walkthrough
We split the list, reverse the second part, and interleave them.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Three passes
Common Mistakes
- ❌ Not finding middle correctly
✅ Use slow/fast pointers
Try It Yourself
Palindrome Linked List - Check if palindrome
Matrix Problems
2D array manipulation and traversal.
Set Matrix Zeroes
What You'll Learn
Using first row and column as markers.
Real-World Context
"Set entire row and column to zero if element is zero."
Breaking It Down
- Check if first row/column have zero
- Use first row/column to mark
- Iterate matrix, mark rows/columns
- Set marked rows/columns to zero
- Handle first row/column separately
def setZeroes(matrix):
if not matrix or not matrix[0]:
return
m, n = len(matrix), len(matrix[0])
first_row_zero = any(matrix[0][j] == 0 for j in range(n))
first_col_zero = any(matrix[i][0] == 0 for i in range(m))
for i in range(1, m):
for j in range(1, n):
if matrix[i][j] == 0:
matrix[i][0] = 0
matrix[0][j] = 0
for i in range(1, m):
if matrix[i][0] == 0:
for j in range(n):
matrix[i][j] = 0
for j in range(1, n):
if matrix[0][j] == 0:
for i in range(m):
matrix[i][j] = 0
if first_row_zero:
for j in range(n):
matrix[0][j] = 0
if first_col_zero:
for i in range(m):
matrix[i][0] = 0Line-by-Line Walkthrough
We use the first row and column to record which rows and columns need to be zeroed.
Complexity Analysis
- Time: O(M*N)
- Space: O(1)
- In-place modification
Common Mistakes
- ❌ Using extra space
✅ Use first row and column as markers
Try It Yourself
Game of Life - Similar in-place modification
Spiral Matrix
What You'll Learn
Directional traversal in matrix.
Real-World Context
"Traverse matrix in spiral order."
Breaking It Down
- Initialize boundaries
- While within bounds
- Traverse right, down, left, up
- Update boundaries after each direction
def spiralOrder(matrix):
if not matrix or not matrix[0]:
return []
result = []
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
# Traverse right
for j in range(left, right + 1):
result.append(matrix[top][j])
top += 1
# Traverse down
for i in range(top, bottom + 1):
result.append(matrix[i][right])
right -= 1
# Traverse left
if top <= bottom:
for j in range(right, left - 1, -1):
result.append(matrix[bottom][j])
bottom -= 1
# Traverse up
if left <= right:
for i in range(bottom, top - 1, -1):
result.append(matrix[i][left])
left += 1
return resultLine-by-Line Walkthrough
We peel the matrix layer by layer, updating the boundaries.
Complexity Analysis
- Time: O(M*N)
- Space: O(1)
- Visit each cell once
Common Mistakes
- ❌ Not updating boundaries correctly
✅ Update after each direction traversal
Try It Yourself
Spiral Matrix II - Generate spiral matrix
Rotate Image
What You'll Learn
In-place matrix rotation.
Real-World Context
"Rotate matrix 90 degrees clockwise in-place."
Breaking It Down
- Transpose the matrix
- Reverse each row
def rotate(matrix):
n = len(matrix)
# Transpose
for i in range(n):
for j in range(i, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# Reverse each row
for row in matrix:
row.reverse()Line-by-Line Walkthrough
Transposing swaps elements across diagonal, reversing rows completes the rotation.
Complexity Analysis
- Time: O(N²)
- Space: O(1)
- In-place operations
Common Mistakes
- ❌ Rotating element by element
✅ Use transpose and reverse
Try It Yourself
Rotate Image - Counter-clockwise
Word Search
What You'll Learn
DFS with backtracking in matrix.
Real-World Context
"Find if word exists in matrix."
Breaking It Down
- For each cell, if matches first char
- DFS in four directions
- Mark visited, backtrack
- Return true if found
def exist(board, word):
if not board or not board[0]:
return False
m, n = len(board), len(board[0])
def dfs(i, j, index):
if index == len(word):
return True
if i < 0 or i >= m or j < 0 or j >= n or board[i][j] != word[index]:
return False
temp = board[i][j]
board[i][j] = '#'
found = dfs(i+1, j, index+1) or dfs(i-1, j, index+1) or dfs(i, j+1, index+1) or dfs(i, j-1, index+1)
board[i][j] = temp
return found
for i in range(m):
for j in range(n):
if board[i][j] == word[0] and dfs(i, j, 0):
return True
return FalseLine-by-Line Walkthrough
We try to match the word starting from each cell, using DFS to explore paths.
Complexity Analysis
- Time: O(M*N*4^L)
- Space: O(L)
- DFS recursion depth
Common Mistakes
- ❌ Not marking visited
✅ Temporarily mark cells to prevent reuse
Try It Yourself
Word Search II - Find all words
String Problems
String manipulation and pattern matching.
Longest Substring Without Repeating Characters
What You'll Learn
Sliding window with hash set.
Real-World Context
"Find length of longest substring without repeating characters."
Breaking It Down
- Use sliding window with hash set
- Expand right, add to set
- If duplicate, move left until removed
- Update max length
def lengthOfLongestSubstring(s):
char_set = set()
left = 0
max_length = 0
for right in range(len(s)):
while s[right] in char_set:
char_set.remove(s[left])
left += 1
char_set.add(s[right])
max_length = max(max_length, right - left + 1)
return max_lengthLine-by-Line Walkthrough
The window expands until a duplicate is found, then contracts from the left.
Complexity Analysis
- Time: O(N)
- Space: O(min(N, 128))
- Sliding window
Common Mistakes
- ❌ Using brute force
✅ Use sliding window
Try It Yourself
Longest Substring with At Most K Distinct Characters
Longest Repeating Character Replacement
What You'll Learn
Sliding window with character count.
Real-World Context
"Find longest substring after replacing k characters."
Breaking It Down
- Use sliding window
- Track max frequency in window
- If window size - max_freq > k, shrink left
- Update max length
def characterReplacement(s, k):
count = {}
left = 0
max_length = 0
max_freq = 0
for right in range(len(s)):
count[s[right]] = count.get(s[right], 0) + 1
max_freq = max(max_freq, count[s[right]])
if (right - left + 1) - max_freq > k:
count[s[left]] -= 1
left += 1
max_length = max(max_length, right - left + 1)
return max_lengthLine-by-Line Walkthrough
We maintain a window where the number of replacements needed is <= k.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Fixed alphabet size
Common Mistakes
- ❌ Not tracking max frequency
✅ Keep track of highest count in window
Try It Yourself
Max Consecutive Ones III - Similar with flips
Minimum Window Substring
What You'll Learn
Sliding window with character counts.
Real-World Context
"Find minimum window containing all characters of t in s."
Breaking It Down
- Use two pointers
- Expand right until window has all chars
- Shrink left while maintaining validity
- Update min window
def minWindow(s, t):
if not t or not s:
return ""
from collections import Counter
t_count = Counter(t)
required = len(t_count)
left = 0
min_len = float('inf')
min_start = 0
window_count = Counter()
formed = 0
for right in range(len(s)):
window_count[s[right]] += 1
if s[right] in t_count and window_count[s[right]] == t_count[s[right]]:
formed += 1
while left <= right and formed == required:
if right - left + 1 < min_len:
min_len = right - left + 1
min_start = left
window_count[s[left]] -= 1
if s[left] in t_count and window_count[s[left]] < t_count[s[left]]:
formed -= 1
left += 1
return "" if min_len == float('inf') else s[min_start:min_start + min_len]Line-by-Line Walkthrough
We expand the window until it contains all characters, then shrink from left.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Fixed alphabet
Common Mistakes
- ❌ Not shrinking window
✅ Shrink when window is valid
Try It Yourself
Substring with Concatenation of All Words
Valid Anagram
What You'll Learn
Character count comparison.
Real-World Context
"Check if two strings are anagrams."
Breaking It Down
- If lengths differ, false
- Count characters in first string
- Decrement for second string
- Check all counts are zero
def isAnagram(s, t):
if len(s) != len(t):
return False
count = [0] * 26
for c in s:
count[ord(c) - ord('a')] += 1
for c in t:
count[ord(c) - ord('a')] -= 1
return all(c == 0 for c in count)Line-by-Line Walkthrough
Anagrams have the same character counts.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- Fixed size count array
Common Mistakes
- ❌ Sorting strings
✅ Use count array for efficiency
Try It Yourself
Group Anagrams - Group strings by anagrams
Group Anagrams
What You'll Learn
Hash map with sorted strings as keys.
Real-World Context
"Group strings that are anagrams."
Breaking It Down
- Use hash map with sorted string as key
- For each string, sort and add to map
- Return the groups
def groupAnagrams(strs):
from collections import defaultdict
groups = defaultdict(list)
for s in strs:
key = ''.join(sorted(s))
groups[key].append(s)
return list(groups.values())Line-by-Line Walkthrough
Anagrams sort to the same string.
Complexity Analysis
- Time: O(N K log K)
- Space: O(N K)
- Sorting each string
Common Mistakes
- ❌ Using count arrays as keys
✅ Sort strings for simple keys
Try It Yourself
Valid Anagram - Check two strings
Valid Parentheses
What You'll Learn
Stack for parentheses matching.
Real-World Context
"Check if string has valid parentheses."
Breaking It Down
- Use stack
- For each char, if open, push
- If close, check if matches top
- Stack empty at end
def isValid(s):
stack = []
mapping = {')': '(', '}': '{', ']': '['}
for c in s:
if c in mapping:
if not stack or stack[-1] != mapping[c]:
return False
stack.pop()
else:
stack.append(c)
return not stackLine-by-Line Walkthrough
Stack keeps track of open parentheses.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- Stack usage
Common Mistakes
- ❌ Not checking order
✅ Use stack to match pairs
Try It Yourself
Generate Parentheses - Generate valid combinations
Valid Palindrome
What You'll Learn
Two pointers for palindrome check.
Real-World Context
"Check if string is palindrome ignoring case and non-alphanumeric."
Breaking It Down
- Use two pointers
- Skip non-alphanumeric
- Compare lowercase chars
- Pointers meet or cross
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 TrueLine-by-Line Walkthrough
We compare from both ends, skipping invalid characters.
Complexity Analysis
- Time: O(N)
- Space: O(1)
- In-place check
Common Mistakes
- ❌ Not ignoring case
✅ Convert to lowercase
Try It Yourself
Valid Palindrome II - Delete one character
Longest Palindromic Substring
What You'll Learn
Expand around centers.
Real-World Context
"Find longest palindromic substring."
Breaking It Down
- For each center, expand
- Check odd and even lengths
- Track max length and start
def longestPalindrome(s):
if not s:
return ""
start = 0
max_len = 1
def expand(left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return right - left - 1
for i in range(len(s)):
len1 = expand(i, i)
len2 = expand(i, i+1)
curr_len = max(len1, len2)
if curr_len > max_len:
max_len = curr_len
start = i - (curr_len - 1) // 2
return s[start:start + max_len]Line-by-Line Walkthrough
We try expanding from each possible center.
Complexity Analysis
- Time: O(N²)
- Space: O(1)
- Expansion from each center
Common Mistakes
- ❌ DP approach
✅ Expand around centers for simplicity
Try It Yourself
Palindromic Substrings - Count all palindromes
Palindromic Substrings
What You'll Learn
Count palindromes by expanding.
Real-World Context
"Count number of palindromic substrings."
Breaking It Down
- For each center, expand and count
- Odd and even lengths
def countSubstrings(s):
count = 0
def expand(left, right):
nonlocal count
while left >= 0 and right < len(s) and s[left] == s[right]:
count += 1
left -= 1
right += 1
for i in range(len(s)):
expand(i, i) # odd
expand(i, i+1) # even
return countLine-by-Line Walkthrough
Each valid expansion is a palindrome.
Complexity Analysis
- Time: O(N²)
- Space: O(1)
- Expansion from each center
Common Mistakes
- ❌ Brute force check
✅ Expand around centers
Try It Yourself
Longest Palindromic Substring - Find longest
Tree Problems
Binary tree traversal and manipulation.
Maximum Depth of Binary Tree
What You'll Learn
Recursive tree traversal.
Real-World Context
"Find maximum depth of binary tree."
Breaking It Down
- If root is null, return 0
- Return 1 + max(left depth, right depth)
def maxDepth(root):
if not root:
return 0
return 1 + max(maxDepth(root.left), maxDepth(root.right))Line-by-Line Walkthrough
The depth is 1 plus the maximum of subtree depths.
Complexity Analysis
- Time: O(N)
- Space: O(H)
- Tree traversal
Common Mistakes
- ❌ Iterative approach
✅ Recursive is simpler
Try It Yourself
Minimum Depth of Binary Tree - Find minimum depth
Same Tree
What You'll Learn
Recursive tree comparison.
Real-World Context
"Check if two trees are identical."
Breaking It Down
- If both null, true
- If one null, false
- If values differ, false
- Recurse on left and right
def isSameTree(p, q):
if not p and not q:
return True
if not p or not q:
return False
if p.val != q.val:
return False
return isSameTree(p.left, q.left) and isSameTree(p.right, q.right)Line-by-Line Walkthrough
We check structure and values recursively.
Complexity Analysis
- Time: O(N)
- Space: O(H)
- Tree traversal
Common Mistakes
- ❌ Not checking null cases
✅ Handle null nodes properly
Try It Yourself
Symmetric Tree - Check if mirror
Invert/Flip Binary Tree
What You'll Learn
Recursive tree inversion.
Real-World Context
"Invert a binary tree."
Breaking It Down
- If root null, return
- Swap left and right
- Recurse on left and right
def invertTree(root):
if not root:
return None
root.left, root.right = root.right, root.left
invertTree(root.left)
invertTree(root.right)
return rootLine-by-Line Walkthrough
Swap subtrees and recurse.
Complexity Analysis
- Time: O(N)
- Space: O(H)
- Tree traversal
Common Mistakes
- ❌ Not recursing
✅ Invert subtrees too
Try It Yourself
Same Tree - Check if inverted matches original
Binary Tree Maximum Path Sum
What You'll Learn
Recursive path sum calculation.
Real-World Context
"Find maximum path sum in binary tree."
Breaking It Down
- Use helper to return max path from node
- Update global max with path through node
- Return max single path
def maxPathSum(root):
max_sum = float('-inf')
def helper(node):
nonlocal max_sum
if not node:
return 0
left = max(helper(node.left), 0)
right = max(helper(node.right), 0)
current = node.val + left + right
max_sum = max(max_sum, current)
return node.val + max(left, right)
helper(root)
return max_sumLine-by-Line Walkthrough
For each node, consider path through it, update global max.
Complexity Analysis
- Time: O(N)
- Space: O(H)
- Tree traversal
Common Mistakes
- ❌ Not considering negative values
✅ Take max with 0 for paths
Try It Yourself
Path Sum - Check if path sums to target
Binary Tree Level Order Traversal
What You'll Learn
BFS with queue.
Real-World Context
"Traverse tree level by level."
Breaking It Down
- Use queue
- While queue not empty
- Process level, add children
- Collect levels
def levelOrder(root):
if not root:
return []
from collections import deque
queue = deque([root])
result = []
while queue:
level = []
for _ in range(len(queue)):
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 resultLine-by-Line Walkthrough
BFS processes nodes level by level.
Complexity Analysis
- Time: O(N)
- Space: O(W)
- Queue usage
Common Mistakes
- ❌ DFS approach
✅ Use BFS for levels
Try It Yourself
Zigzag Level Order - Alternate directions
Serialize and Deserialize Binary Tree
What You'll Learn
Preorder traversal with null markers.
Real-World Context
"Serialize and deserialize binary tree."
Breaking It Down
- Serialize: preorder with null markers
- Deserialize: reconstruct from list
def serialize(root):
def helper(node):
if not node:
return ['null']
return [str(node.val)] + helper(node.left) + helper(node.right)
return ','.join(helper(root))
def deserialize(data):
vals = data.split(',')
i = 0
def helper():
nonlocal i
if vals[i] == 'null':
i += 1
return None
node = TreeNode(int(vals[i]))
i += 1
node.left = helper()
node.right = helper()
return node
return helper()Line-by-Line Walkthrough
Preorder traversal with null markers preserves structure.
Complexity Analysis
- Time: O(N)
- Space: O(N)
- String and recursion
Common Mistakes
- ❌ Level order
✅ Preorder preserves structure
Try It Yourself
Construct Binary Tree from Preorder and Inorder Traversal
Subtree of Another Tree
What You'll Learn
Recursive subtree check.
Real-World Context
"Check if one tree is subtree of another."
Breaking It Down
- Check if trees are same
- Or check left or right subtrees
def isSubtree(root, subRoot):
def isSame(p, q):
if not p and not q:
return True
if not p or not q:
return False
return p.val == q.val and isSame(p.left, q.left) and isSame(p.right, q.right)
if not root:
return False
if isSame(root, subRoot):
return True
return isSubtree(root.left, subRoot) or isSubtree(root.right, subRoot)Line-by-Line Walkthrough
Check if subRoot matches any subtree of root.
Complexity Analysis
- Time: O(N*M)
- Space: O(H)
- Traversal and comparison
Common Mistakes
- ❌ Not checking all subtrees
✅ Recurse on left and right
Try It Yourself
Same Tree - Check if identical
Construct Binary Tree from Preorder and Inorder Traversal
What You'll Learn
Recursive construction using indices.
Real-World Context
"Build tree from preorder and inorder."
Breaking It Down
- Use inorder to find root position
- Recurse on left and right subtrees
def buildTree(preorder, inorder):
if not preorder or not inorder:
return None
root_val = preorder[0]
root = TreeNode(root_val)
mid = inorder.index(root_val)
root.left = buildTree(preorder[1:mid+1], inorder[:mid])
root.right = buildTree(preorder[mid+1:], inorder[mid+1:])
return rootLine-by-Line Walkthrough
Preorder gives root, inorder gives left/right split.
Complexity Analysis
- Time: O(N²)
- Space: O(N)
- Index search and slicing
Common Mistakes
- ❌ Wrong slicing
✅ Careful with indices
Try It Yourself
Construct from Inorder and Postorder
Validate Binary Search Tree
What You'll Learn
Inorder traversal check.
Real-World Context
"Check if tree is valid BST."
Breaking It Down
- Inorder traversal should be sorted
- Check ascending order
def isValidBST(root):
def inorder(node, prev):
if not node:
return True
if not inorder(node.left, prev):
return False
if prev[0] is not None and node.val <= prev[0]:
return False
prev[0] = node.val
return inorder(node.right, prev)
return inorder(root, [None])Line-by-Line Walkthrough
Each node must be within bounds from ancestors.
Complexity Analysis
- Time: O(N)
- Space: O(H)
- Tree traversal
Common Mistakes
- ❌ Not checking bounds
✅ Pass min/max to recursion
Try It Yourself
Kth Smallest Element in a BST
Kth Smallest Element in a BST
What You'll Learn
Inorder traversal for sorted order.
Real-World Context
"Find kth smallest element in BST."
Breaking It Down
- Inorder traversal
- Count until kth
def kthSmallest(root, k):
count = 0
result = None
def inorder(node):
nonlocal count, result
if not node or result is not None:
return
inorder(node.left)
count += 1
if count == k:
result = node.val
return
inorder(node.right)
inorder(root)
return resultLine-by-Line Walkthrough
Inorder gives sorted order, count to k.
Complexity Analysis
- Time: O(N)
- Space: O(H)
- Traversal
Common Mistakes
- ❌ Not using BST property
✅ Inorder gives order
Try It Yourself
Validate Binary Search Tree
Lowest Common Ancestor of BST
What You'll Learn
BST property for LCA.
Real-World Context
"Find LCA in BST."
Breaking It Down
- If both < root, go left
- If both > root, go right
- Else root is LCA
def lowestCommonAncestor(root, p, q):
if p.val < root.val and q.val < root.val:
return lowestCommonAncestor(root.left, p, q)
elif p.val > root.val and q.val > root.val:
return lowestCommonAncestor(root.right, p, q)
else:
return rootLine-by-Line Walkthrough
LCA is where paths diverge.
Complexity Analysis
- Time: O(H)
- Space: O(H)
- Tree height
Common Mistakes
- ❌ Iterative approach
✅ Recursive is simple
Try It Yourself
Lowest Common Ancestor of Binary Tree - Without BST property
Implement Trie (Prefix Tree)
What You'll Learn
Trie data structure.
Real-World Context
"Implement trie with insert, search, startsWith."
Breaking It Down
- Node with children map and isEnd
- Insert: traverse/create nodes
- Search: traverse and check isEnd
- StartsWith: traverse
class Trie:
def __init__(self):
self.children = {}
self.isEnd = False
def insert(self, word):
node = self
for c in word:
if c not in node.children:
node.children[c] = Trie()
node = node.children[c]
node.isEnd = True
def search(self, word):
node = self
for c in word:
if c not in node.children:
return False
node = node.children[c]
return node.isEnd
def startsWith(self, prefix):
node = self
for c in prefix:
if c not in node.children:
return False
node = node.children[c]
return TrueLine-by-Line Walkthrough
Trie stores prefixes efficiently.
Complexity Analysis
- Time: O(L)
- Space: O(N)
- Length of word
Common Mistakes
- ❌ Using hash set
✅ Trie for prefixes
Try It Yourself
Add and Search Word - With wildcards
Add and Search Word - Data structure design
What You'll Learn
Trie with wildcards.
Real-World Context
"Implement word dictionary with wildcards."
Breaking It Down
- Use trie
- For search, handle '.' as any char
- DFS for wildcards
class WordDictionary:
def __init__(self):
self.trie = {}
def addWord(self, word):
node = self.trie
for c in word:
if c not in node:
node[c] = {}
node = node[c]
node['#'] = True
def search(self, word):
def dfs(node, i):
if i == len(word):
return '#' in node
c = word[i]
if c == '.':
for child in node:
if child != '#' and dfs(node[child], i+1):
return True
return False
else:
if c not in node:
return False
return dfs(node[c], i+1)
return dfs(self.trie, 0)Line-by-Line Walkthrough
DFS handles the wildcard by trying all possibilities.
Complexity Analysis
- Time: O(26^L)
- Space: O(N)
- Wildcard branching
Common Mistakes
- ❌ No DFS for '.'
✅ Use DFS to explore all
Try It Yourself
Implement Trie - Without wildcards
Word Search II
What You'll Learn
Trie + DFS for word search.
Real-World Context
"Find all words in board."
Breaking It Down
- Build trie with words
- DFS from each cell
- Mark visited, check trie
def findWords(board, words):
trie = {}
for word in words:
node = trie
for c in word:
if c not in node:
node[c] = {}
node = node[c]
node['#'] = word
m, n = len(board), len(board[0])
result = set()
def dfs(i, j, node):
if '#' in node:
result.add(node['#'])
for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
ni, nj = i + di, j + dj
if 0 <= ni < m and 0 <= nj < n and board[ni][nj] in node:
temp = board[ni][nj]
board[ni][nj] = '#'
dfs(ni, nj, node[temp])
board[ni][nj] = temp
for i in range(m):
for j in range(n):
if board[i][j] in trie:
temp = board[i][j]
board[i][j] = '#'
dfs(i, j, trie[temp])
board[i][j] = temp
return list(result)Line-by-Line Walkthrough
Trie prunes invalid paths.
Complexity Analysis
- Time: O(M*N*4^L)
- Space: O(N)
- Board traversal
Common Mistakes
- ❌ No trie
✅ Use trie to optimize
Try It Yourself
Word Search - Single word
Heap Problems
Priority queues and heap operations.
Top K Frequent Elements
What You'll Learn
Heap for top k elements.
Real-World Context
"Find k most frequent elements."
Breaking It Down
- Count frequencies
- Use heap to keep top k
- Return top k
def topKFrequent(nums, k):
from collections import Counter
import heapq
count = Counter(nums)
heap = []
for num, freq in count.items():
heapq.heappush(heap, (freq, num))
if len(heap) > k:
heapq.heappop(heap)
return [num for freq, num in heap]Line-by-Line Walkthrough
Min-heap keeps smallest of top k.
Complexity Analysis
- Time: O(N log K)
- Space: O(N)
- Heap operations
Common Mistakes
- ❌ Sorting
✅ Use heap for efficiency
Try It Yourself
Kth Largest Element in Array
Find Median from Data Stream
What You'll Learn
Two heaps for median.
Real-World Context
"Find median in running stream."
Breaking It Down
- Max-heap for lower half
- Min-heap for upper half
- Balance heaps
- Median from heaps
class MedianFinder:
def __init__(self):
self.small = [] # max-heap
self.large = [] # min-heap
def addNum(self, num):
import heapq
heapq.heappush(self.small, -num)
heapq.heappush(self.large, -heapq.heappop(self.small))
if len(self.large) > len(self.small):
heapq.heappush(self.small, -heapq.heappop(self.large))
def findMedian(self):
if len(self.small) > len(self.large):
return -self.small[0]
return (-self.small[0] + self.large[0]) / 2Line-by-Line Walkthrough
Max-heap for lower, min-heap for upper, keep balanced.
Complexity Analysis
- Time: O(log N)
- Space: O(N)
- Heap operations
Common Mistakes
- ❌ Single heap
✅ Two heaps for balance
Try It Yourself
Sliding Window Median