💡 No 'R' balls left, must check if 'WW' can be cleared.
prune
No balls left to remove 'WW', return -1 and conclude no solution
No balls in hand to remove 'WW', so return -1 for this path and propagate up.
💡 All attempts fail, so the board cannot be cleared.
Line:memo[key] = -1 if res == float('inf') else res
return memo[key]
💡 The final answer is -1, meaning no solution exists.
from collections import Counter
import sys
sys.setrecursionlimit(10**7)
def findMinStep(board: str, hand: str) -> int:
hand_count = Counter(hand) # STEP 1
memo = {} # STEP 1
def remove_consecutive(s): # STEP 7, 16, 19
i = 0
while i < len(s):
j = i
while j < len(s) and s[j] == s[i]:
j += 1
if j - i >= 3:
s = s[:i] + s[j:]
i = 0
else:
i += 1
return s
def dp(s, hand_count):
if not s:
return 0
key = (s, tuple(sorted(hand_count.items())))
if key in memo:
return memo[key]
res = float('inf')
i = 0
while i < len(s):
j = i
while j < len(s) and s[j] == s[i]:
j += 1
balls_needed = 3 - (j - i) # STEP 3,5,9,10,15,18,20
c = s[i]
if hand_count[c] >= balls_needed: # STEP 4,6,12,18,20
hand_count[c] -= balls_needed # STEP 6,16,18
new_s = s[:i] + s[j:]
new_s = remove_consecutive(new_s) # STEP 7,16,19
temp = dp(new_s, hand_count) # STEP 8,11,17
if temp != -1:
res = min(res, balls_needed + temp) # STEP 14
hand_count[c] += balls_needed
i = j
memo[key] = -1 if res == float('inf') else res # STEP 13,20
return memo[key]
return dp(board, hand_count) # STEP 2
if __name__ == '__main__':
print(findMinStep("WRRBBW", "RB")) # Output: -1
print(findMinStep("WWRRBBWW", "WRBRW")) # Output: 2
📊
Zuma Game (Min Moves to Clear) - Watch the Algorithm Execute, Step by Step
Watching each insertion attempt and recursive call reveals how interval DP breaks down the problem and why some states are impossible to clear.
Step 1/20
·Active fill★Answer cell
Item 0 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
?
?
?
?
?
?
Item 0 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
R
R
B
B
W
full board
Item 0 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
R
R
B
B
W
'W' group
Item 0 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
R
R
B
B
W
not enough 'W' balls
Item 1 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
R
R
B
B
W
'RR' group
Item 1 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
R
R
B
B
W
insert 'R' ball
Item 1 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
B
B
B
W
?
board after removal
Item 1 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
B
B
B
W
?
recursive call
Item 0 - wt:0 val:0
i\w
0
1
2
3
4
i=0
W
B
B
B
W
not enough 'W' balls
Item 1 - wt:0 val:0
i\w
0
1
2
3
4
i=0
W
W
?
?
?
remove 'BBB'
Item 0 - wt:0 val:0
i\w
0
1
i=0
W
W
recursive call
Item 0 - wt:0 val:0
i\w
0
1
i=0
W
W
not enough 'W' balls
Item 0 - wt:0 val:0
i\w
0
1
i=0
-1
?
no solution
Item 1 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
B
B
B
W
-1
no solution
Item 3 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
R
R
B
B
W
'BB' group
Item 3 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
W
R
R
W
?
?
board after removal
Item 0 - wt:0 val:0
i\w
0
1
2
3
i=0
W
R
R
W
recursive call
Item 1 - wt:0 val:0
i\w
0
1
2
3
i=0
W
R
R
W
insert 'R' ball
Item 1 - wt:0 val:0
i\w
0
1
i=0
W
W
board after removal
Item 0 - wt:0 val:0
i\w
0
1
2
3
4
5
i=0
-1
?
?
?
?
?
final no solution
Key Takeaways
✓ Interval DP breaks the problem into smaller substrings and uses memoization to avoid recomputation.
This is hard to see from code alone because recursion and memoization interplay is subtle.
✓ The algorithm tries all groups and possible insertions, pruning impossible paths early.
Visualizing each insertion attempt clarifies why some states fail and others succeed.
✓ Chain reactions from removing groups simplify the board and reduce problem size.
Watching remove_consecutive in action reveals how the board changes dynamically.
Practice
(1/5)
1. Consider the following Python function implementing the minimum falling path sum using bottom-up DP:
def minFallingPathSum(matrix):
n = len(matrix)
dp = matrix[0][:]
for i in range(1, n):
new_dp = [0]*n
for j in range(n):
best = dp[j]
if j > 0:
best = min(best, dp[j-1])
if j < n - 1:
best = min(best, dp[j+1])
new_dp[j] = matrix[i][j] + best
dp = new_dp
return min(dp)
matrix = [[2,1,3],[6,5,4],[7,8,9]]
print(minFallingPathSum(matrix))
2. You are given a grid where some cells are blocked and others are free. You need to find the number of unique paths from the top-left corner to the bottom-right corner, moving only down or right, but you cannot pass through blocked cells. Which algorithmic approach guarantees an efficient and correct solution for this problem?
easy
A. Dynamic Programming that builds solutions using previously computed subproblems while skipping blocked cells
B. Pure brute force recursion exploring all paths without memoization
C. Greedy algorithm that always moves right if possible, else down
D. Dijkstra's shortest path algorithm treating grid cells as graph nodes
Solution
Step 1: Understand problem constraints
The problem requires counting all unique paths avoiding obstacles, which involves overlapping subproblems and optimal substructure.
Step 2: Identify suitable algorithm
Dynamic Programming efficiently computes the number of paths by reusing results and handling obstacles by zeroing paths through blocked cells.
Final Answer:
Option A -> Option A
Quick Check:
DP handles obstacles and overlapping subproblems correctly [OK]
Hint: DP handles obstacles and overlapping subproblems correctly [OK]
Common Mistakes:
Thinking greedy can find all paths
Using brute force without pruning
Confusing shortest path with counting paths
3. Consider the following Python code implementing the space-optimized DP solution for counting unique paths with obstacles. Given the input grid below, what is the value of the dp array after processing the second row (i=1)?
Input grid:
[[0,0,0],
[0,1,0],
[0,0,0]]
def uniquePathsWithObstacles(obstacleGrid):
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp = [0]*n
dp[0] = 1 if obstacleGrid[0][0] == 0 else 0
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[j] = 0
else:
if j > 0:
dp[j] += dp[j-1]
return dp[-1]
easy
A. [1, 1, 1]
B. [1, 1, 0]
C. [1, 0, 0]
D. [1, 0, 1]
Solution
Step 1: Initialize dp after first row (i=0)
dp starts as [1,0,0]. After processing first row (all zeros), dp updates to [1,1,1].
Step 2: Process second row (i=1)
At j=0, obstacleGrid[1][0]=0, dp[0] remains 1. At j=1, obstacleGrid[1][1]=1 (obstacle), dp[1] set to 0. At j=2, obstacleGrid[1][2]=0, dp[2] += dp[1] -> dp[2] = 1 + 0 = 1. Resulting dp: [1, 0, 1]
Final Answer:
Option D -> Option D
Quick Check:
dp correctly zeroes obstacle cell and accumulates paths [OK]
Hint: Obstacles zero dp cells, dp[j] accumulates from dp[j-1] [OK]
Common Mistakes:
Forgetting to zero dp[j] on obstacle
Mis-updating dp[j] before dp[j-1]
Confusing row and column indices
4. You need to find the number of distinct ways to move from the top-left corner to the bottom-right corner of an m x n grid, moving only down or right at each step. Which algorithmic approach guarantees an efficient and optimal solution for this problem?
easy
A. Pure brute force recursion exploring all possible paths without memoization
B. Greedy algorithm that always moves towards the direction with fewer remaining steps
C. Dynamic Programming that builds solutions from smaller subproblems using a grid-based state representation
D. Divide and conquer by splitting the grid into quadrants and combining results
Solution
Step 1: Understand problem constraints
The problem requires counting all unique paths with only right and down moves, which naturally forms overlapping subproblems.
Step 2: Identify suitable approach
Dynamic Programming efficiently solves overlapping subproblems by storing intermediate results, unlike greedy or pure recursion which are either incorrect or inefficient.
Final Answer:
Option C -> Option C
Quick Check:
DP uses subproblem reuse -> optimal and efficient [OK]
Hint: Counting paths with overlapping subproblems -> DP [OK]
Common Mistakes:
Thinking greedy can find all paths
Using pure recursion without memoization
5. The following code attempts to compute the number of unique paths in an m x n grid using a space-optimized DP approach:
def uniquePaths(m, n):
dp = [1] * n
for i in range(1, m):
for j in range(1, n):
dp[j] = dp[j] + dp[j]
return dp[-1]
What is the bug in this code?
medium
A. Line 3 should start loop from 0 instead of 1
B. Line 4 incorrectly doubles dp[j] instead of adding dp[j-1]
C. Line 5 should return dp[0] instead of dp[-1]
D. Line 2 initializes dp with wrong size
Solution
Step 1: Analyze inner loop update
Line 4 updates dp[j] by adding dp[j] to itself, doubling the value instead of adding dp[j-1].
Step 2: Identify correct update
The correct update is dp[j] += dp[j - 1] to accumulate paths from left and top cells.
Final Answer:
Option B -> Option B
Quick Check:
Doubling dp[j] breaks path count logic -> bug at line 4 [OK]
Hint: Check dp update uses dp[j-1], not dp[j] twice [OK]
Common Mistakes:
Using dp[j] + dp[j] instead of dp[j] + dp[j-1]
Off-by-one errors in loops
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.