Jump into concepts and practice - no test required
or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
▶
Steps
setup
Initialize DP array with -inf
Create a 3D dp array of size n x n x n filled with -inf to represent impossible states initially.
💡 Initializing with -inf ensures only reachable states will be updated later, preventing invalid paths from influencing results.
Line:dp = [[[-float('inf')] * n for _ in range(n)] for __ in range(n)]
💡 All states start as unreachable except the base case to be set next.
setup
Set base case dp[0][0][0]
Set dp[0][0][0] to grid[0][0] (0) as both players start at the top-left corner.
💡 This base case anchors the DP, representing the starting point with collected cherries equal to the grid cell value.
Line:dp[0][0][0] = grid[0][0]
💡 The starting position is reachable with initial cherries collected.
fill_cells
Step 1: Iterate over r1 and r2 to fill dp for step=1
For step=1, consider all valid r1 and r2 positions and compute c1 and c2. Skip invalid or blocked cells.
💡 At each step, players move one cell right or down; this step explores all possible positions after the first move.
Line:for r1 in range(max(0, step-(n-1)), min(n, step+1)):
c1 = step - r1
for r2 in range(max(0, step-(n-1)), min(n, step+1)):
c2 = step - r2
💡 Positions are constrained by the step number and grid size; invalid positions are ignored.
fill_cells
Step 1: Update dp for r1=0, c1=1 and r2=0, c2=1
Calculate dp[0][1][0] by checking previous states and adding cherries at (0,1) and (0,1). Avoid double counting since positions are the same.
💡 When both players land on the same cell, count cherries only once to avoid overcounting.
Line:if grid[r1][c1] != -1 and grid[r2][c2] != -1:
candidates.append(dp[r1][c1-1][r2])
max_prev = max(candidates)
val = max_prev + grid[r1][c1]
if r1 != r2 or c1 != c2:
val += grid[r2][c2]
dp[r1][c1][r2] = max(dp[r1][c1][r2], val)
💡 The dp value updates by adding cherries at the new positions, respecting overlap.
fill_cells
Step 1: Update dp for r1=1, c1=0 and r2=1, c2=0
Calculate dp[1][0][1] by checking previous states and adding cherries at (1,0) and (1,0). Avoid double counting since positions are the same.
💡 Similar to previous step, handle overlapping positions carefully.
Line:if grid[r1][c1] != -1 and grid[r2][c2] != -1:
candidates.append(dp[r1-1][c1][r2-1])
max_prev = max(candidates)
val = max_prev + grid[r1][c1]
if r1 != r2 or c1 != c2:
val += grid[r2][c2]
dp[r1][c1][r2] = max(dp[r1][c1][r2], val)
💡 The dp value updates by adding cherries at the new positions, respecting overlap.
fill_cells
Step 2: Iterate over r1 and r2 for step=2
For step=2, compute all valid (r1, c1) and (r2, c2) positions and prepare to update dp accordingly.
💡 Step 2 corresponds to players moving further down or right; all reachable positions are considered.
Line:for r1 in range(max(0, step-(n-1)), min(n, step+1)):
c1 = step - r1
for r2 in range(max(0, step-(n-1)), min(n, step+1)):
c2 = step - r2
💡 Positions are constrained by grid size and step number.
fill_cells
Step 2: Update dp for r1=1, c1=1 and r2=1, c2=1
Calculate dp[1][1][1] by checking all valid previous states and adding cherries at (1,1) and (1,1), avoiding double counting.
💡 Players meet at the same cell; count cherries once.
Line:candidates = []
if r1 > 0 and r2 > 0:
candidates.append(dp[r1-1][c1][r2-1])
if r1 > 0 and c2 > 0:
candidates.append(dp[r1-1][c1][r2])
if c1 > 0 and r2 > 0:
candidates.append(dp[r1][c1-1][r2-1])
if c1 > 0 and c2 > 0:
candidates.append(dp[r1][c1-1][r2])
max_prev = max(candidates)
val = max_prev + grid[r1][c1]
if r1 != r2 or c1 != c2:
val += grid[r2][c2]
dp[r1][c1][r2] = max(dp[r1][c1][r2], val)
💡 The dp value accumulates cherries from previous best states plus current cell cherries.
fill_cells
Step 2: Update dp for r1=2, c1=0 and r2=2, c2=0
Calculate dp[2][0][2] by checking previous states and adding cherries at (2,0) and (2,0), avoiding double counting.
💡 Players meet at the same cell; count cherries once.
Line:candidates = []
if r1 > 0 and r2 > 0:
candidates.append(dp[r1-1][c1][r2-1])
if r1 > 0 and c2 > 0:
candidates.append(dp[r1-1][c1][r2])
if c1 > 0 and r2 > 0:
candidates.append(dp[r1][c1-1][r2-1])
if c1 > 0 and c2 > 0:
candidates.append(dp[r1][c1-1][r2])
max_prev = max(candidates)
val = max_prev + grid[r1][c1]
if r1 != r2 or c1 != c2:
val += grid[r2][c2]
dp[r1][c1][r2] = max(dp[r1][c1][r2], val)
💡 The dp value accumulates cherries from previous best states plus current cell cherries.
fill_cells
Step 3: Iterate over r1 and r2 for step=3
For step=3, compute all valid (r1, c1) and (r2, c2) positions and prepare to update dp accordingly.
💡 Step 3 corresponds to players moving further; all reachable positions are considered.
Line:for r1 in range(max(0, step-(n-1)), min(n, step+1)):
c1 = step - r1
for r2 in range(max(0, step-(n-1)), min(n, step+1)):
c2 = step - r2
💡 Positions are constrained by grid size and step number.
fill_cells
Step 3: Update dp for r1=2, c1=1 and r2=2, c2=1
Calculate dp[2][1][2] by checking previous states and adding cherries at (2,1) and (2,1), avoiding double counting.
💡 Players meet at the same cell; count cherries once.
Line:candidates = []
if r1 > 0 and r2 > 0:
candidates.append(dp[r1-1][c1][r2-1])
if r1 > 0 and c2 > 0:
candidates.append(dp[r1-1][c1][r2])
if c1 > 0 and r2 > 0:
candidates.append(dp[r1][c1-1][r2-1])
if c1 > 0 and c2 > 0:
candidates.append(dp[r1][c1-1][r2])
max_prev = max(candidates)
val = max_prev + grid[r1][c1]
if r1 != r2 or c1 != c2:
val += grid[r2][c2]
dp[r1][c1][r2] = max(dp[r1][c1][r2], val)
💡 The dp value accumulates cherries from previous best states plus current cell cherries.
fill_cells
Step 4: Iterate over r1 and r2 for step=4
For step=4, compute all valid (r1, c1) and (r2, c2) positions and prepare to update dp accordingly.
💡 Step 4 is the final step where both players reach bottom-right corner.
Line:for r1 in range(max(0, step-(n-1)), min(n, step+1)):
c1 = step - r1
for r2 in range(max(0, step-(n-1)), min(n, step+1)):
c2 = step - r2
💡 Positions are constrained by grid size and step number.
fill_cells
Step 4: Update dp for r1=2, c1=2 and r2=2, c2=2
Calculate dp[2][2][2] by checking previous states and adding cherries at (2,2) and (2,2), avoiding double counting.
💡 Players meet at the same cell; count cherries once.
Line:candidates = []
if r1 > 0 and r2 > 0:
candidates.append(dp[r1-1][c1][r2-1])
if r1 > 0 and c2 > 0:
candidates.append(dp[r1-1][c1][r2])
if c1 > 0 and r2 > 0:
candidates.append(dp[r1][c1-1][r2-1])
if c1 > 0 and c2 > 0:
candidates.append(dp[r1][c1-1][r2])
max_prev = max(candidates)
val = max_prev + grid[r1][c1]
if r1 != r2 or c1 != c2:
val += grid[r2][c2]
dp[r1][c1][r2] = max(dp[r1][c1][r2], val)
💡 The dp value accumulates cherries from previous best states plus current cell cherries.
reconstruct
Final Step: Return the maximum cherries collected
Return the maximum cherries collected when both players reach the bottom-right corner, or 0 if unreachable.
💡 The answer is the value in dp[n-1][n-1][n-1], representing both players at the destination.
Line:return max(dp[n-1][n-1][n-1], 0)
💡 The DP table has captured the maximum cherries collectible by two coordinated paths.
from typing import List
def cherryPickup(grid: List[List[int]]) -> int:
n = len(grid) # STEP 1
dp = [[[-float('inf')] * n for _ in range(n)] for __ in range(n)] # STEP 1
dp[0][0][0] = grid[0][0] # STEP 2
for step in range(1, 2 * (n - 1) + 1): # STEP 3
for r1 in range(max(0, step - (n - 1)), min(n, step + 1)): # STEP 3
c1 = step - r1 # STEP 3
if c1 < 0 or c1 >= n:
continue
for r2 in range(max(0, step - (n - 1)), min(n, step + 1)): # STEP 3
c2 = step - r2 # STEP 3
if c2 < 0 or c2 >= n:
continue
if grid[r1][c1] == -1 or grid[r2][c2] == -1: # STEP 4
continue
candidates = [] # STEP 5
if r1 > 0 and r2 > 0:
candidates.append(dp[r1 - 1][c1][r2 - 1])
if r1 > 0 and c2 > 0:
candidates.append(dp[r1 - 1][c1][r2])
if c1 > 0 and r2 > 0:
candidates.append(dp[r1][c1 - 1][r2 - 1])
if c1 > 0 and c2 > 0:
candidates.append(dp[r1][c1 - 1][r2])
max_prev = max(candidates) if candidates else -float('inf') # STEP 5
if max_prev == -float('inf'):
continue
val = max_prev + grid[r1][c1] # STEP 5
if r1 != r2 or c1 != c2:
val += grid[r2][c2] # STEP 5
dp[r1][c1][r2] = max(dp[r1][c1][r2], val) # STEP 5
return max(dp[n - 1][n - 1][n - 1], 0) # STEP 6
📊
Cherry Pickup (Two Paths Simultaneously) - Watch the Algorithm Execute, Step by Step
Watching each update to the DP table reveals how the algorithm considers all possible previous states and coordinates two paths to maximize cherry collection.
Step 1/13
·Active fill★Answer cell
Item 0 - wt:0 val:0
i\w
0
1
2
i=0
-∞
-∞
-∞
i=1
-∞
-∞
-∞
i=2
-∞
-∞
-∞
-inf (unreachable)
Item 0 - wt:0 val:0
i\w
0
1
2
i=0
0
-∞
-∞
i=1
-∞
-∞
-∞
i=2
-∞
-∞
-∞
start dp[0][0][0] = 0
Item 0 - wt:0 val:0
i\w
0
1
2
i=0
-∞
-∞
-∞
i=1
-∞
-∞
-∞
i=2
-∞
-∞
-∞
r1=0,c1=1
Item 0 - wt:1 val:1
i\w
0
1
2
i=0
-∞
1
-∞
i=1
-∞
-∞
-∞
i=2
-∞
-∞
-∞
prev dp[0][0][0]=0
Item 1 - wt:1 val:1
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
-∞
-∞
i=2
-∞
-∞
-∞
prev dp[0][0][0]=0
Item 0 - wt:0 val:0
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
-∞
-∞
i=2
-∞
-∞
-∞
r1=0,c1=2
Item 1 - wt:1 val:2
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
2
-∞
i=2
-∞
-∞
-∞
prev dp[0][1][0]=1
Item 2 - wt:1 val:3
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
2
-∞
i=2
3
-∞
-∞
prev dp[1][1][1]=2
Item 0 - wt:0 val:0
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
2
-∞
i=2
3
4
-∞
r1=1,c1=2
Item 2 - wt:1 val:4
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
2
-∞
i=2
3
4
-∞
prev dp[2][0][2]=3
Item 0 - wt:0 val:0
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
2
-∞
i=2
3
4
-∞
r1=2,c1=2
Item 2 - wt:1 val:5
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
2
-∞
i=2
3
4
5
prev dp[2][1][2]=4
Item 2 - wt:1 val:5
i\w
0
1
2
i=0
-∞
1
-∞
i=1
1
2
-∞
i=2
3
4
5
answer dp[2][2][2] = 5
Key Takeaways
✓ The DP table tracks two players' positions simultaneously, enabling coordination to maximize cherry collection.
This simultaneous tracking is hard to grasp from code alone but becomes clear when seeing the DP table update.
✓ Each dp cell depends on up to four previous states, reflecting all possible moves of both players at the previous step.
Visualizing these dependencies clarifies the fill order and why the algorithm considers multiple previous states.
✓ When both players land on the same cell, cherries are counted only once to avoid double counting.
This subtle condition is easier to understand when watching the step-by-step updates and the effect on dp values.
Practice
(1/5)
1. You are given a string representing a sequence of colored balls on a board and a multiset of balls in hand. Your goal is to clear the board by inserting balls from your hand such that any group of three or more consecutive balls of the same color is removed, possibly triggering chain reactions. Which algorithmic approach guarantees finding the minimum number of insertions needed to clear the board optimally?
easy
A. Interval dynamic programming with memoization exploiting optimal substructure
B. Topological sorting of ball groups followed by greedy removals
C. Pure brute force recursion without memoization exploring all insertions
D. Greedy insertion of balls at the earliest possible position to remove groups
Solution
Step 1: Understand problem structure
The problem requires minimizing insertions to clear the board, which involves exploring overlapping subproblems and optimal substructure.
Step 2: Identify suitable algorithm
Interval DP with memoization efficiently explores all intervals and hand states, guaranteeing minimal moves by considering all splits and removals.
Final Answer:
Option A -> Option A
Quick Check:
Interval DP is known for solving interval removal problems optimally [OK]
Using brute force without memoization leads to exponential time
Misapplying topological sort which is unrelated here
2. The following code attempts to solve the Burst Balloons problem using bottom-up DP. Identify the line containing the subtle bug that causes incorrect results on some inputs.
def maxCoins(nums):
n = len(nums)
dp = [[0] * (n + 2) for _ in range(n + 2)]
nums = nums + [1, 1]
for length in range(2, n + 2):
for i in range(0, n + 2 - length):
j = i + length
for k in range(i + 1, j):
coins = dp[i][k] + nums[i] * nums[k] * nums[j] + dp[k][j]
if coins > dp[i][j]:
dp[i][j] = coins
return dp[0][n + 1]
medium
A. Line 4: Adding virtual balloons at the end instead of both ends
B. Line 3: Initializing dp with size (n+2) x (n+2)
C. Line 7: Looping length from 2 to n+2
D. Line 10: Calculating coins using dp[i][k] + nums[i]*nums[k]*nums[j] + dp[k][j]
Solution
Step 1: Check virtual balloon addition
The code adds [1,1] only at the end of nums, but the algorithm requires adding 1 at both the start and end to handle boundaries correctly.
Step 2: Consequences of missing virtual balloon at start
Without the leading 1, dp indices and coin calculations become incorrect, causing index errors or wrong coin counts at boundaries.
Final Answer:
Option A -> Option A
Quick Check:
Virtual balloons must be added at both ends [OK]
Hint: Always add virtual balloons at both ends to avoid boundary errors [OK]
Common Mistakes:
Adding virtual balloons only at one end
Incorrect dp table size
Wrong loop boundaries
3. Consider the following buggy code for the Dungeon Game problem. Which line contains the subtle bug that causes incorrect minimum health calculation?
from typing import List
class Solution:
def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
m, n = len(dungeon), len(dungeon[0])
dp = [[float('inf')] * (n + 1) for _ in range(m + 1)]
dp[m][n - 1] = 0 # Bug here
dp[m - 1][n] = 1
for i in range(m - 1, -1, -1):
for j in range(n - 1, -1, -1):
need = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j]
dp[i][j] = max(1, need)
return dp[0][0]
medium
A. Line computing need = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j]
B. Line initializing dp[m - 1][n] to 1
C. Line initializing dp[m][n - 1] to 0 instead of 1
D. Line setting dp[i][j] = max(1, need)
Solution
Step 1: Check base case initialization
The cell dp[m][n - 1] must be initialized to 1 to represent minimum health needed beyond the princess cell.
Step 2: Identify impact of wrong initialization
Setting dp[m][n - 1] to 0 allows health to drop to zero, violating constraints and causing incorrect dp propagation.
Final Answer:
Option C -> Option C
Quick Check:
Base case must be 1, not 0 [OK]
Hint: Base case dp[m][n-1] must be 1, not 0 [OK]
Common Mistakes:
Incorrect base case initialization
Forgetting max(1, need) clamp
4. What is the time and space complexity of the optimal space-optimized bottom-up DP solution for the Unique Paths II problem on an m x n grid with obstacles?
medium
A. Time: O(m*n), Space: O(m*n)
B. Time: O(m+n), Space: O(m+n)
C. Time: O(m*n), Space: O(n)
D. Time: O(2^(m+n)), Space: O(m*n)
Solution
Step 1: Analyze time complexity
The algorithm iterates over each cell once in nested loops: O(m*n).
Step 2: Analyze space complexity
Uses a single 1D dp array of length n, so space is O(n).
Final Answer:
Option C -> Option C
Quick Check:
Nested loops over m and n, dp array size n [OK]
Hint: Nested loops over m and n, dp array size n [OK]
Common Mistakes:
Confusing recursion stack space with dp array
Assuming full 2D dp array used
Mistaking linear time for exponential
5. Suppose the Stone Game is modified so that players can pick stones from either end multiple times (i.e., reuse piles after picking). Which of the following changes correctly adapts the DP solution to handle this variant?
hard
A. Use a bottom-up DP with a sliding window over piles, but do not change the recurrence relation
B. Keep the same interval DP but add a loop to consider picking the same pile multiple times in one turn
C. Change dp state to represent maximum difference with unlimited picks and use a 1D dp array updated iteratively
D. Switch to a greedy approach since DP no longer applies when reuse is allowed
Solution
Step 1: Understand the impact of reuse
Allowing reuse means piles are infinite or replenished, so intervals no longer shrink and the problem resembles unbounded knapsack.
Step 2: Adapt DP state and recurrence
We need a 1D dp array representing maximum difference for each possible pile count, updated iteratively considering unlimited picks, similar to unbounded knapsack.
Step 3: Reject incorrect options
Interval DP with multiple picks per turn or unchanged recurrence won't handle reuse correctly; greedy fails due to opponent's optimal play.
Final Answer:
Option C -> Option C
Quick Check:
Reuse -> unbounded knapsack style DP with 1D array [OK]
Hint: Reuse -> unbounded knapsack DP, not interval DP [OK]