Skip to content

필사 모드: FAANG Coding Interview Mastery: Backtracking, Heap, and Advanced Patterns

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

FAANG Coding Interview Mastery: Backtracking, Heap, and Advanced Patterns

This guide covers backtracking, heap/priority queue, monotonic stack, Trie, and bit manipulation patterns that appear frequently in FAANG interviews.

1. Backtracking Patterns

The Backtracking Template

Backtracking explores a decision tree to find all solutions satisfying a constraint.

def backtrack(state, choices):

Base case: found a valid solution

if is_solution(state):

result.append(state[:]) # Save a copy, not a reference

return

for choice in choices:

Choose

make_choice(state, choice)

Explore (recurse)

backtrack(state, remaining_choices)

Unchoose (undo — this is the key!)

undo_choice(state, choice)

**Core Principles**:

- Always save a copy of state to results (not a reference)

- State must be identical before and after each recursive call

- Use pruning to eliminate unnecessary branches early

Subsets (LeetCode 78)

def subsets(nums: list) -> list:

result = []

def backtrack(start, current):

result.append(current[:]) # Save current subset

for i in range(start, len(nums)):

current.append(nums[i]) # Choose

backtrack(i + 1, current) # Explore

current.pop() # Unchoose

backtrack(0, [])

return result

- Time: O(n \* 2^n)

- Space: O(n) (call stack)

Subsets II — Handling Duplicates (LeetCode 90)

def subsetsWithDup(nums: list) -> list:

nums.sort() # Sort for duplicate detection

result = []

def backtrack(start, current):

result.append(current[:])

for i in range(start, len(nums)):

Skip duplicates at the same recursion level

if i > start and nums[i] == nums[i-1]:

continue

current.append(nums[i])

backtrack(i + 1, current)

current.pop()

backtrack(0, [])

return result

Permutations (LeetCode 46)

def permute(nums: list) -> list:

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

Visited array approach (more efficient)

def permute_v2(nums: list) -> list:

result = []

visited = [False] * len(nums)

def backtrack(current):

if len(current) == len(nums):

result.append(current[:])

return

for i in range(len(nums)):

if visited[i]:

continue

visited[i] = True

current.append(nums[i])

backtrack(current)

current.pop()

visited[i] = False

backtrack([])

return result

Permutations II — Handling Duplicates (LeetCode 47)

def permuteUnique(nums: list) -> list:

nums.sort()

result = []

visited = [False] * len(nums)

def backtrack(current):

if len(current) == len(nums):

result.append(current[:])

return

for i in range(len(nums)):

if visited[i]:

continue

Skip if same value and previous element not yet visited

if i > 0 and nums[i] == nums[i-1] and not visited[i-1]:

continue

visited[i] = True

current.append(nums[i])

backtrack(current)

current.pop()

visited[i] = False

backtrack([])

return result

Combination Sum (LeetCode 39)

def combinationSum(candidates: list, target: int) -> list:

result = []

def backtrack(start, current, remaining):

if remaining == 0:

result.append(current[:])

return

if remaining < 0:

return

for i in range(start, len(candidates)):

current.append(candidates[i])

backtrack(i, current, remaining - candidates[i]) # Can reuse same element

current.pop()

backtrack(0, [], target)

return result

Combination Sum II (LeetCode 40)

def combinationSum2(candidates: list, target: int) -> list:

candidates.sort()

result = []

def backtrack(start, current, remaining):

if remaining == 0:

result.append(current[:])

return

for i in range(start, len(candidates)):

if candidates[i] > remaining:

break

if i > start and candidates[i] == candidates[i-1]:

continue

current.append(candidates[i])

backtrack(i + 1, current, remaining - candidates[i])

current.pop()

backtrack(0, [], target)

return result

N-Queens (LeetCode 51)

def solveNQueens(n: int) -> list:

result = []

cols = set()

diag1 = set() # row - col (top-right to bottom-left)

diag2 = set() # row + col (top-left to bottom-right)

def backtrack(row, board):

if row == n:

result.append([''.join(r) for r in board])

return

for col in range(n):

if col in cols or (row - col) in diag1 or (row + col) in diag2:

continue

cols.add(col)

diag1.add(row - col)

diag2.add(row + col)

board[row][col] = 'Q'

backtrack(row + 1, board)

cols.remove(col)

diag1.remove(row - col)

diag2.remove(row + col)

board[row][col] = '.'

board = [['.' for _ in range(n)] for _ in range(n)]

backtrack(0, board)

return result

Word Search (LeetCode 79)

def exist(board: list, word: str) -> bool:

rows, cols = len(board), len(board[0])

def backtrack(r, c, idx):

if idx == len(word):

return True

if r < 0 or r >= rows or c < 0 or c >= cols:

return False

if board[r][c] != word[idx]:

return False

temp = board[r][c]

board[r][c] = '#' # Mark as visited

found = (

backtrack(r+1, c, idx+1) or

backtrack(r-1, c, idx+1) or

backtrack(r, c+1, idx+1) or

backtrack(r, c-1, idx+1)

)

board[r][c] = temp # Restore

return found

for r in range(rows):

for c in range(cols):

if backtrack(r, c, 0):

return True

return False

2. Heap / Priority Queue Patterns

Python heapq Complete Guide

Min-heap (default)

min_heap = []

heapq.heappush(min_heap, 3)

heapq.heappush(min_heap, 1)

heapq.heappush(min_heap, 2)

print(heapq.heappop(min_heap)) # 1 (minimum)

Max-heap (negate values)

max_heap = []

heapq.heappush(max_heap, -3)

heapq.heappush(max_heap, -1)

heapq.heappush(max_heap, -2)

print(-heapq.heappop(max_heap)) # 3 (maximum)

Build heap from list

nums = [3, 1, 4, 1, 5, 9]

heapq.heapify(nums) # O(n)

n largest / n smallest

heapq.nlargest(3, nums) # Top 3 largest

heapq.nsmallest(3, nums) # Top 3 smallest

Tuple heap: (priority, value)

heap = [(1, 'task_a'), (3, 'task_b'), (2, 'task_c')]

heapq.heapify(heap)

priority, task = heapq.heappop(heap) # (1, 'task_a')

Kth Largest Element (LeetCode 215)

def findKthLargest(nums: list, k: int) -> int:

Maintain a min-heap of size k

min_heap = []

for num in nums:

heapq.heappush(min_heap, num)

if len(min_heap) > k:

heapq.heappop(min_heap)

return min_heap[0] # kth largest

QuickSelect O(n) average

def findKthLargest_quickselect(nums: list, k: int) -> int:

def quickselect(left, right, k_smallest):

pivot = nums[random.randint(left, right)]

lo, hi, mid = left, right, left

while mid <= hi:

if nums[mid] < pivot:

nums[lo], nums[mid] = nums[mid], nums[lo]

lo += 1

mid += 1

elif nums[mid] > pivot:

nums[hi], nums[mid] = nums[mid], nums[hi]

hi -= 1

else:

mid += 1

if k_smallest < lo:

return quickselect(left, lo - 1, k_smallest)

elif k_smallest >= mid:

return quickselect(mid, right, k_smallest)

else:

return nums[lo]

return quickselect(0, len(nums) - 1, len(nums) - k)

Top K Frequent Elements (LeetCode 347)

from collections import Counter

def topKFrequent(nums: list, k: int) -> list:

count = Counter(nums)

Maintain a min-heap of size k using (freq, num) pairs

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]

Bucket sort O(n)

def topKFrequent_bucket(nums: list, k: int) -> list:

count = Counter(nums)

buckets = [[] for _ in range(len(nums) + 1)]

for num, freq in count.items():

buckets[freq].append(num)

result = []

for i in range(len(buckets) - 1, -1, -1):

result.extend(buckets[i])

if len(result) >= k:

return result[:k]

return result

Merge K Sorted Lists (LeetCode 23)

class ListNode:

def __init__(self, val=0, next=None):

self.val = val

self.next = next

def mergeKLists(lists: list) -> ListNode:

heap = []

Push the first node from each list

for i, node in enumerate(lists):

if node:

heapq.heappush(heap, (node.val, i, node))

dummy = ListNode(0)

curr = dummy

while heap:

val, i, node = heapq.heappop(heap)

curr.next = node

curr = curr.next

if node.next:

heapq.heappush(heap, (node.next.val, i, node.next))

return dummy.next

- Time: O(N log k), where N = total nodes, k = number of lists

Find Median from Data Stream (LeetCode 295)

The two-heap pattern is a key interview technique.

class MedianFinder:

def __init__(self):

self.small = [] # Max-heap (lower half, stored as negatives)

self.large = [] # Min-heap (upper half)

def addNum(self, num: int) -> None:

Add to small heap, then rebalance

heapq.heappush(self.small, -num)

If max of small > min of large, move element

if self.small and self.large and (-self.small[0] > self.large[0]):

heapq.heappush(self.large, -heapq.heappop(self.small))

Balance sizes

if len(self.small) > len(self.large) + 1:

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) -> float:

if len(self.small) > len(self.large):

return -self.small[0]

return (-self.small[0] + self.large[0]) / 2

Task Scheduler (LeetCode 621)

from collections import Counter

def leastInterval(tasks: list, n: int) -> int:

count = Counter(tasks)

max_heap = [-c for c in count.values()]

heapq.heapify(max_heap)

time = 0

queue = [] # (count, available_time)

while max_heap or queue:

time += 1

if max_heap:

cnt = 1 + heapq.heappop(max_heap)

if cnt:

queue.append((cnt, time + n))

if queue and queue[0][1] == time:

heapq.heappush(max_heap, queue.pop(0)[0])

return time

3. Monotonic Stack Patterns

The Monotonic Stack Concept

A monotonic stack maintains elements in monotonically increasing or decreasing order.

- **Monotonically increasing stack**: pop when a smaller or equal element arrives (finds next smaller element)

- **Monotonically decreasing stack**: pop when a larger or equal element arrives (finds next greater element)

Daily Temperatures (LeetCode 739)

def dailyTemperatures(temperatures: list) -> list:

n = len(temperatures)

result = [0] * n

stack = [] # stores indices

for i, temp in enumerate(temperatures):

Pop while current temperature is warmer

while stack and temperatures[stack[-1]] < temp:

idx = stack.pop()

result[idx] = i - idx

stack.append(i)

return result

Next Greater Element (LeetCode 496)

def nextGreaterElement(nums1: list, nums2: list) -> list:

next_greater = {}

stack = []

for num in nums2:

while stack and stack[-1] < num:

next_greater[stack.pop()] = num

stack.append(num)

Elements remaining in stack have no next greater element

while stack:

next_greater[stack.pop()] = -1

return [next_greater[n] for n in nums1]

Largest Rectangle in Histogram (LeetCode 84)

def largestRectangleArea(heights: list) -> int:

stack = [] # Monotonically increasing stack (indices)

max_area = 0

heights = heights + [0] # Sentinel to flush the stack at the end

for i, h in enumerate(heights):

while stack and heights[stack[-1]] > h:

height = heights[stack.pop()]

width = i if not stack else i - stack[-1] - 1

max_area = max(max_area, height * width)

stack.append(i)

return max_area

Trapping Rain Water — Stack Approach (LeetCode 42)

def trap(height: list) -> int:

stack = [] # Monotonically decreasing stack

water = 0

for i, h in enumerate(height):

while stack and height[stack[-1]] < h:

bottom = stack.pop()

if not stack:

break

bounded_height = min(height[stack[-1]], h) - height[bottom]

width = i - stack[-1] - 1

water += bounded_height * width

stack.append(i)

return water

4. Trie (Prefix Tree) Patterns

Trie Implementation

class TrieNode:

def __init__(self):

self.children = {} # or [None] * 26 for lowercase letters

self.is_end = False

class Trie:

def __init__(self):

self.root = TrieNode()

def insert(self, word: str) -> None:

node = self.root

for char in word:

if char not in node.children:

node.children[char] = TrieNode()

node = node.children[char]

node.is_end = True

def search(self, word: str) -> bool:

node = self.root

for char in word:

if char not in node.children:

return False

node = node.children[char]

return node.is_end

def startsWith(self, prefix: str) -> bool:

node = self.root

for char in prefix:

if char not in node.children:

return False

node = node.children[char]

return True

Design Add and Search Words (LeetCode 211)

class WordDictionary:

def __init__(self):

self.root = TrieNode()

def addWord(self, word: str) -> None:

node = self.root

for char in word:

if char not in node.children:

node.children[char] = TrieNode()

node = node.children[char]

node.is_end = True

def search(self, word: str) -> bool:

def dfs(node, i):

if i == len(word):

return node.is_end

char = word[i]

if char == '.':

Wildcard: explore all children

for child in node.children.values():

if dfs(child, i + 1):

return True

return False

if char not in node.children:

return False

return dfs(node.children[char], i + 1)

return dfs(self.root, 0)

Word Search II (LeetCode 212)

def findWords(board: list, words: list) -> list:

Build Trie from word list

root = TrieNode()

for word in words:

node = root

for char in word:

if char not in node.children:

node.children[char] = TrieNode()

node = node.children[char]

node.is_end = True

rows, cols = len(board), len(board[0])

result = set()

def dfs(r, c, node, path):

if node.is_end:

result.add(path)

Continue searching for longer words

if r < 0 or r >= rows or c < 0 or c >= cols:

return

char = board[r][c]

if char not in node.children or char == '#':

return

board[r][c] = '#' # Mark visited

for dr, dc in [(0,1), (0,-1), (1,0), (-1,0)]:

dfs(r+dr, c+dc, node.children[char], path+char)

board[r][c] = char # Restore

for r in range(rows):

for c in range(cols):

dfs(r, c, root, '')

return list(result)

5. Bit Manipulation Patterns

XOR Tricks and Bit Operation Fundamentals

Basic bit operations

a = 0b1010 # 10

b = 0b1100 # 12

print(a & b) # AND: 0b1000 = 8

print(a | b) # OR: 0b1110 = 14

print(a ^ b) # XOR: 0b0110 = 6

print(~a) # NOT: -11 (two's complement)

print(a << 1) # Left shift: 0b10100 = 20

print(a >> 1) # Right shift: 0b0101 = 5

Useful bit tricks

n = 12

print(n & (n-1)) # Remove lowest set bit: 8

print(n & (-n)) # Isolate lowest set bit: 4

print(n ^ n) # XOR with itself = 0

print(0 ^ n) # XOR with 0 = n

Single Number (LeetCode 136)

def singleNumber(nums: list) -> int:

XOR all numbers: pairs cancel out to 0

result = 0

for num in nums:

result ^= num

return result

Counting Bits (LeetCode 338)

def countBits(n: int) -> list:

dp = [0] * (n + 1)

for i in range(1, n + 1):

Number of 1s in i = number of 1s in (i >> 1) + lowest bit

dp[i] = dp[i >> 1] + (i & 1)

return dp

Reverse Bits (LeetCode 190)

def reverseBits(n: int) -> int:

result = 0

for _ in range(32):

result = (result << 1) | (n & 1)

n >>= 1

return result

Number of 1 Bits — Hamming Weight (LeetCode 191)

def hammingWeight(n: int) -> int:

count = 0

while n:

n &= (n - 1) # Remove the lowest set bit

count += 1

return count

Python built-in

def hammingWeight_v2(n: int) -> int:

return bin(n).count('1')

6. Real Interview Strategies

How to Ask for Hints When Stuck

Staying silent when stuck is the worst thing you can do. Try phrases like these:

"I'm running into [specific issue] with my current approach.

Am I missing something obvious here?"

"I'm thinking of using [specific data structure / algorithm].

Does that seem like a reasonable direction?"

**Hidden Rule**: Even when a hint is given, how you incorporate it matters just as much as the answer.

How to Discuss Code Optimization

When asked to optimize:

"My current solution runs in O(n^2) time and O(n) space.

I think using [specific data structure / algorithm]

could bring this down to O(n log n). Should I implement that?"

How to Explain Time and Space Complexity

Always state the reasoning, not just the answer:

"The time complexity is O(n log n).

The outer loop runs n times, and each iteration performs

a heap operation costing O(log n).

The space complexity is O(k) because

we maintain a heap of at most k elements."

The Interview Execution Framework

1. Understand the Problem (5 min)

- Create your own examples

- Ask about edge cases

2. Explain Your Approach (5 min)

- Brute force first

- Then discuss optimizations

3. Code (15-20 min)

- Talk through your logic as you code

- Use meaningful variable names

4. Test (5 min)

- Trace through your own example

- Verify edge cases

7. Practice Quiz

**Answer**: To skip duplicates only at the same level of recursion, not across different levels.

**Explanation**: Using "i > 0" would skip elements that were already used in a parent call, causing valid combinations to be missed. "i > start" only skips when the same value appears twice at the current recursion depth, which is exactly the condition that produces duplicate subsets.

**Answer**: To achieve O(1) median retrieval and O(log n) insertion.

**Explanation**: Maintaining a sorted array gives O(n) insertion. Using only one heap gives O(n) retrieval. Two balanced heaps (a max-heap for the lower half and a min-heap for the upper half) let us read the median from the tops of the heaps in O(1), while heap push and pop are O(log n).

**Answer**: To force all remaining elements to be popped from the stack after the loop ends.

**Explanation**: The sentinel value 0 is smaller than any valid height, so it triggers the while-loop to pop every remaining index from the stack. This eliminates the need for a separate post-loop cleanup step. You could also handle the remaining elements manually after the loop — both approaches are correct.

**Answer**: Two key XOR properties: a XOR a = 0 (any number XORed with itself is 0), and a XOR 0 = a (any number XORed with 0 is itself).

**Explanation**: All numbers appearing twice cancel out to 0 when XORed together. The number appearing once is XORed with 0, leaving itself. XOR is commutative and associative, so the order does not matter.

**Answer**: To share common prefix traversal across multiple words and prune branches that cannot lead to any word.

**Explanation**: Searching for each word individually costs O(words _ cells _ 4^L) where L is word length. A Trie allows multiple words sharing a common prefix to share the same DFS path. When the current board path is not a prefix of any word in the Trie, we can prune immediately, dramatically reducing the total number of DFS calls.

Summary

**Complexity Reference**:

| Pattern | Time | Space |

| ------------------------- | ----------- | ------------------- |

| Backtracking Subsets | O(n \* 2^n) | O(n) |

| Backtracking Permutations | O(n \* n!) | O(n) |

| Heap — Kth element | O(n log k) | O(k) |

| Merge K Lists | O(N log k) | O(k) |

| Monotonic Stack | O(n) | O(n) |

| Trie Insert/Search | O(L) | O(ALPHABET _ N _ L) |

| Bit XOR trick | O(n) | O(1) |

**Interview Preparation Roadmap**:

1. Backtracking: Subsets → Permutations → Combination Sum → N-Queens

2. Heap: Kth Largest → Top K → Merge K Lists → Median Finder

3. Monotonic Stack: Daily Temperatures → Largest Rectangle

4. Trie: Implement Trie → Word Search II

5. Bit Manipulation: Single Number → Counting Bits → Power of Two

현재 단락 (1/501)

This guide covers backtracking, heap/priority queue, monotonic stack, Trie, and bit manipulation pat...

작성 글자: 0원문 글자: 16,119작성 단락: 0/501