↓Skip to main content
  1. LeetCode/

LeetCode 488: Zuma Game

·3 mins· ·
LeetCode Hard Dynamic-Programming Memoization Breadth-First-Search String Stack
Wei Yi Chung
Author
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
Table of Contents

Meta Data
#

Difficulty: hard First Attempt: 2026-07-08

  • Submissions: Wrong Answer ×2 → Accepted

Approach
#

An insertion triggers chain removals, so the board cannot be split into independent ranges. But the board has at most 16 balls and the hand at most 5, so the whole (board, hand) can be the state for a memoized DFS:

  • Empty board: 0 steps.
  • For each kind of ball in hand (sorted, skipping repeats) and each insertion point, insert, clear, recurse, and take the minimum + 1.

Pruning: only two kinds of positions are worth trying:

  1. Before a ball of the same color: building a group.
  2. Between two equal balls of a different color from the one inserted: e.g. W into RR gives RWR, letting each R later join other Rs.

Clearing is a loop: find a run of length >= 3, delete it, and rescan from the start until nothing can be removed.

My Attempts
#

VersionProblem
Attempt 1Inserted only next to same-colored balls, missing the “split two equal balls” move
Attempt 2Added the split condition, but inserted with b[:j + 1] + h[i] + b[j + 1:], placing the ball after b[j]; a split needs it between b[j - 1] and b[j], so nothing was actually split
ACChanged it to b[:j] + h[i] + b[j:]

Both fail on board = "RRWWRRBBRR", hand = "WB": the answer is 2 (first split an RR with a W to trigger a chain), and both returned -1.

Solution
#

from functools import cache

class Solution:
    def findMinStep(self, board: str, hand: str) -> int:
        def clean(s: str) -> str:
            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:]              # delete, then rescan (chain removals)
                    i = 0
                else:
                    i = j
            return s

        @cache
        def dfs(b: str, h: str) -> float:
            if not b:
                return 0
            best = float("inf")
            for i in range(len(h)):
                if i > 0 and h[i] == h[i - 1]:
                    continue                       # try each kind of ball once
                for j in range(len(b) + 1):
                    same = j < len(b) and b[j] == h[i]
                    split = 0 < j < len(b) and b[j - 1] == b[j] != h[i]
                    if same or split:
                        nb = clean(b[:j] + h[i] + b[j:])
                        best = min(best, 1 + dfs(nb, h[:i] + h[i + 1:]))
            return best

        res = dfs(board, "".join(sorted(hand)))
        return -1 if res == float("inf") else res

Complexity
#

The states are the boards reachable by insertions and removals times the subsets of the hand, exponential in the worst case, but with a short board and hand, memoization plus pruning passes.

Mistakes to Avoid
#

  • Inserting only next to same-colored balls: splitting two equal balls is sometimes the only solution.
  • An insertion index off by one.
  • Sort the hand first so repeated balls can be skipped.

Interview Explanation
#

Insertions cause chain removals, so the problem does not split into subproblems, but the board and hand are short, so I memoize a DFS on the whole (board, hand). Each step tries every kind of ball in hand at every insertion point, but only before a same-colored ball or between two equal balls, simulates the removals, recurses, and keeps the fewest steps.

Related

LeetCode 1745: Palindrome Partitioning IV
·2 mins
LeetCode Hard Dynamic-Programming Interval-Dp String
LeetCode 1745 note: build is_pal[i][j] with interval DP, then try both cut points; three palindromic parts means true, O(n^2)
LeetCode 3816: Lexicographically Smallest String After Deleting Duplicate Characters
·3 mins
LeetCode Hard Stack Monotonic-Stack Greedy String
LeetCode 3816 note: a monotonic stack that deletes a larger top while another copy exists, then trims duplicate letters off the end since a prefix is always smaller
LeetCode 42: Trapping Rain Water
·2 mins
LeetCode Hard Array Two-Pointers Dynamic-Programming Stack
LeetCode 42 note: each cell holds min(tallest on the left, tallest on the right) - its own height; use prefix and suffix maxima or two pointers
LeetCode 1147: Longest Chunked Palindrome Decomposition
·2 mins
LeetCode Hard Dynamic-Programming Greedy Two-Pointers Rolling-Hash String
LeetCode 1147 note: build strings inward from both ends and cut a pair as soon as they match; cutting early is best, and any leftover middle counts as one chunk
LeetCode 84: Largest Rectangle in Histogram
·3 mins
LeetCode Daily Hard Array Stack Monotonic-Stack Dynamic-Programming Geometry Histogram
Solving the Largest Rectangle in Histogram problem using monotonic stack approach
LeetCode 1411: Number of Ways to Paint N × 3 Grid
·2 mins
LeetCode Hard Dynamic-Programming State-Machine Graph-Coloring
LeetCode 1411 note: a row’s colorings come in two patterns, ABA (ends match) and ABC (all different), 6 each; the next row gives ABA → 3 ABA + 2 ABC and ABC → 2 ABA + 2 ABC