Stage IV: Hard Questions

Greedy Algorithm

Pattern: Advanced Greedy Logic

These problems require a more complex or non-obvious greedy choice, often involving careful consideration of future states or multiple passes.

6. Jump Game II

Problem: You are given a 0-indexed array of integers nums of length n. You are initially positioned at nums[0]. Each element nums[i] represents the maximum length of a forward jump from index i. Return the minimum number of jumps to reach nums[n-1].

Concept Tested: A more advanced greedy choice. Instead of jumping one step at a time, the greedy strategy is to make the jump that reaches the farthest possible index. You maintain a window of the current reachable range and find the best next jump within that range.

Companies: Amazon, Microsoft, Google, Apple

View Python Solution

def jump(nums: list[int]) -> int:
    jumps = 0
    current_farthest = 0
    next_farthest = 0
for i in range(len(nums) - 1):
    next_farthest = max(next_farthest, i + nums[i])
    
    if i == current_farthest:
        jumps += 1
        current_farthest = next_farthest
        
return jumps

7. Task Scheduler

Problem: Given a characters array tasks and a non-negative integer n (the cooldown period), each task takes one unit of time. You need to schedule the tasks such that the same tasks are at least n units of time apart. Return the least number of units of times that the CPU will take to finish all the given tasks.

Concept Tested: A frequency-based greedy approach. The optimal strategy is to always try to schedule the most frequent task. The idle time is determined by the frequency of the most common task and the cooldown period.

Companies: Facebook (Meta), Google, Amazon, Microsoft

View Python Solution


from collections import Counter
def leastInterval(tasks: list[str], n: int) -> int:
    if n == 0:
        return len(tasks)
counts = Counter(tasks)
max_freq = max(counts.values())

# Count how many tasks have the max frequency
num_max_freq_tasks = sum(1 for count in counts.values() if count == max_freq)

# Calculate the number of blocks needed based on the most frequent task
# (max_freq - 1) full blocks, each of size (n + 1)
# plus the number of tasks in the last block
interval_time = (max_freq - 1) * (n + 1) + num_max_freq_tasks

# The result is the max of this calculated time and the actual number of tasks
# (in case there's no idle time needed)
return max(len(tasks), interval_time)

8. Candy

Problem: There are n children standing in a line. You are giving candies to these children subjected to the following requirements: Each child must have at least one candy, and children with a higher rating get more candies than their neighbors. Return the minimum number of candies you need to have.

Concept Tested: A two-pass greedy algorithm. A single greedy pass is insufficient. The first pass from left-to-right ensures every child has more candies than their left neighbor if their rating is higher. The second pass from right-to-left does the same for the right neighbor.

Companies: Google, Amazon, Facebook (Meta)

View Python Solution


def candy(ratings: list[int]) -> int:
    n = len(ratings)
    candies = [1] * n
# First pass: Left to Right
for i in range(1, n):
    if ratings[i] > ratings[i-1]:
        candies[i] = candies[i-1] + 1
        
# Second pass: Right to Left
for i in range(n - 2, -1, -1):
    if ratings[i] > ratings[i+1]:
        candies[i] = max(candies[i], candies[i+1] + 1)
        
return sum(candies)

Backtracking

Pattern: Constraint Satisfaction on a Grid

These are the classic, definitive backtracking problems that require carefully checking a set of complex rules at each step.

6. N-Queens

Problem: The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other. Given an integer n, return all distinct solutions to the n-queens puzzle.

Concept Tested: Advanced constraint satisfaction. For each placement, you must check that the new queen is not on the same column, row, or diagonal as any previously placed queens.

Companies: Amazon, Google, Microsoft, Facebook (Meta)

View Python Solution

def solveNQueens(n: int) -> list[list[str]]:
    cols = set()
    pos_diag = set() # (r + c)
    neg_diag = set() # (r - c)
result = []
board = [["."] * n for _ in range(n)]

def backtrack(r):
    if r == n:
        copy = ["".join(row) for row in board]
        result.append(copy)
        return
        
    for c in range(n):
        if c in cols or (r + c) in pos_diag or (r - c) in neg_diag:
            continue # Prune this path
        
        # Choose
        cols.add(c)
        pos_diag.add(r + c)
        neg_diag.add(r - c)
        board[r][c] = "Q"
        
        # Explore
        backtrack(r + 1)
        
        # Un-choose
        cols.remove(c)
        pos_diag.remove(r + c)
        neg_diag.remove(r - c)
        board[r][c] = "."
        
backtrack(0)
return result

7. Sudoku Solver

Problem: Write a program to solve a Sudoku puzzle by filling the empty cells.

Concept Tested: Grid-based constraint satisfaction. The choice is which number (1-9) to place in an empty cell, and the constraints are that the number must not already exist in the current row, column, or 3x3 sub-box.

Companies: Amazon, Microsoft, Facebook (Meta), Apple, Uber

View Python Solution


def solveSudoku(board: list[list[str]]) -> None:
    def is_valid(r, c, digit):
        for i in range(9):
            if board[i][c] == digit: return False # Check column
            if board[r][i] == digit: return False # Check row
            if board[3 * (r // 3) + i // 3][3 * (c // 3) + i % 3] == digit: return False # Check 3x3 box
        return True
def backtrack(r, c):
    if r == 9: # Reached the end of the board
        return True
    
    # Move to the next row if current column is out of bounds
    next_r, next_c = (r, c + 1) if c < 8 else (r + 1, 0)
    
    if board[r][c] != '.':
        return backtrack(next_r, next_c)
    
    for digit_val in range(1, 10):
        digit_str = str(digit_val)
        if is_valid(r, c, digit_str):
            # Choose
            board[r][c] = digit_str
            # Explore
            if backtrack(next_r, next_c):
                return True
            # Un-choose (backtrack)
            board[r][c] = '.'
    return False

backtrack(0, 0)

Pattern: Backtracking with Other Data Structures

This problem requires combining the backtracking search algorithm with another data structure (a Trie) for massive optimization.

8. Word Search II

Problem: Given an m x n board of characters and a list of words, return all words on the board. Each word must be constructed from letters of sequentially adjacent cells, where "adjacent" cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.

Concept Tested: Combining a backtracking DFS on the grid with a Trie (Prefix Tree). The Trie allows for extremely efficient pruning: if the current path on the board doesn't form a valid prefix in the dictionary, you can stop exploring that path immediately.

Companies: Amazon, Google, Microsoft, Facebook (Meta)

View Python Solution

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_word = False

class Solution: def findWords(self, board: list[list[str]], words: list[str]) -> list[str]: root = TrieNode() for w in words: node = root for c in w: if c not in node.children: node.children[c] = TrieNode() node = node.children[c] node.is_word = True

    ROWS, COLS = len(board), len(board[0])
    result, visited = set(), set()
    
    def backtrack(r, c, node, word):
        if (r < 0 or c < 0 or r >= ROWS or c >= COLS or 
                board[r][c] not in node.children or (r, c) in visited):
            return
        
        # Choose
        visited.add((r, c))
        node = node.children[board[r][c]]
        word += board[r][c]
        if node.is_word:
            result.add(word)
        
        # Explore
        backtrack(r + 1, c, node, word)
        backtrack(r - 1, c, node, word)
        backtrack(r, c + 1, node, word)
        backtrack(r, c - 1, node, word)
        
        # Un-choose
        visited.remove((r, c))
        
    for r in range(ROWS):
        for c in range(COLS):
            backtrack(r, c, root, "")
            
    return list(result)

Dynamic Programming

Pattern: DP on Strings / Sequences

These problems involve complex state transitions based on string or sequence properties.

13. Edit Distance

Concept Tested: A harder variation of the LCS pattern. The recurrence involves three choices: insert, delete, or replace.

Companies: Google, Amazon, Microsoft

View Python Solution

def minDistance(word1: str, word2: str) -> int:
    M, N = len(word1), len(word2)
    dp = [[0] * (N + 1) for _ in range(M + 1)]
for i in range(M + 1): dp[i][N] = M - i
for j in range(N + 1): dp[M][j] = N - j

for i in range(M - 1, -1, -1):
    for j in range(N - 1, -1, -1):
        if word1[i] == word2[j]:
            dp[i][j] = dp[i + 1][j + 1]
        else:
            dp[i][j] = 1 + min(dp[i][j + 1], dp[i + 1][j], dp[i + 1][j + 1])
return dp[0][0]

14. Regular Expression Matching

Concept Tested: A complex 2D DP where the state dp[i][j] represents if s[:i] matches p[:j]. The logic for the * character, which can match zero or more preceding elements, makes the recurrence challenging.

Companies: Facebook (Meta), Google, Amazon, Microsoft

View Python Solution


def isMatch(s: str, p: str) -> bool:
    memo = {}
    def dp(i, j):
        if (i, j) in memo: return memo[(i, j)]
        if j == len(p): return i == len(s)
    first_match = i < len(s) and (p[j] == s[i] or p[j] == '.')

    if j + 1 < len(p) and p[j+1] == '*':
        # Two choices for '*': use it zero times, or use it one time
        res = dp(i, j + 2) or (first_match and dp(i + 1, j))
    else:
        res = first_match and dp(i + 1, j + 1)

    memo[(i,j)] = res
    return res
return dp(0,0)

Pattern: DP on Intervals

For these problems, the DP state is often defined by an interval `dp[i][j]`.

15. Burst Balloons

Concept Tested: The state dp[i][j] is the max coins from bursting balloons in the range (i, j). The key insight is to define the recurrence around the last balloon to be burst in an interval, which splits the problem into two independent subproblems.

Companies: Google, Amazon, Microsoft

View Python Solution

def maxCoins(nums: list[int]) -> int:
    nums = [1] + nums + [1]
    memo = {}
    def dp(l, r):
        if l > r: return 0
        if (l, r) in memo: return memo[(l,r)]
    res = 0
    for i in range(l, r + 1):
        # Assume nums[i] is the last balloon to be burst in (l,r)
        coins = nums[l-1] * nums[i] * nums[r+1]
        coins += dp(l, i - 1) + dp(i + 1, r)
        res = max(res, coins)
    memo[(l,r)] = res
    return res
return dp(1, len(nums) - 2)

Pattern: DP with Multiple States

These problems require more complex states than just a simple index or two.

16. Best Time to Buy and Sell Stock III

Concept Tested: An extension of simpler stock problems. The DP state must include not just the day, but also the number of transactions completed (k) and whether you are currently holding a stock. dp[i][k][0] = max profit on day i with k transactions and no stock.

Companies: Amazon, Microsoft, Google, Bloomberg

View Python Solution

def maxProfit(prices: list[int]) -> int:
    # State:
    # t1_cost: min cost of the first transaction's buy
    # t1_profit: max profit of the first transaction's sell
    # t2_cost: min cost of the second transaction's buy
    # t2_profit: max profit of the second transaction's sell
    t1_cost, t2_cost = float('inf'), float('inf')
    t1_profit, t2_profit = 0, 0
for price in prices:
    # For transaction 1
    t1_cost = min(t1_cost, price)
    t1_profit = max(t1_profit, price - t1_cost)
    # For transaction 2
    t2_cost = min(t2_cost, price - t1_profit)
    t2_profit = max(t2_profit, price - t2_cost)

return t2_profit