Problem

You are playing a variation of the game Zuma.

In this variation of Zuma, there is a single row of colored balls on a board, where each ball can be colored red 'R', yellow 'Y', blue 'B', green 'G', or white 'W'. You also have several colored balls in your hand.

Your goal is to clear all of the balls from the board. On each turn:

  • Pick any ball from your hand and insert it in between two balls in the row or on either end of the row.
  • If there is a group of three or more consecutive balls of the same color , remove the group of balls from the board.
    • If this removal causes more groups of three or more of the same color to form, then continue removing each group until there are none left.
  • If there are no more balls on the board, then you win the game.
  • Repeat this process until you either win or do not have any more balls in your hand.

Given a string board, representing the row of balls on the board, and a string hand, representing the balls in your hand, return _theminimum number of balls you have to insert to clear all the balls from the board. If you cannot clear all the balls from the board using the balls in your hand, return _-1.

Examples

Example 1

1
2
3
4
5
6
7
8
9

    
    
    Input: board = "WRRBBW", hand = "RB"
    Output: -1
    Explanation: It is impossible to clear all the balls. The best you can do is:
    - Insert 'R' so the board becomes WRR _R_ BBW. W _RRR_ BBW -> WBBW.
    - Insert 'B' so the board becomes WBB _B_ W. W _BBB_ W -> WW.
    There are still balls remaining on the board, and you are out of balls to insert.

Example 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

    
    
    Input: board = "WWRRBBWW", hand = "WRBRW"
    Output: 2
    Explanation: To make the board empty:
    - Insert 'R' so the board becomes WWRR _R_ BBWW. WW _RRR_ BBWW -> WWBBWW.
    - Insert 'B' so the board becomes WWBB _B_ WW. WW _BBB_ WW -> _WWWW_ -> empty.
    2 balls from your hand were needed to clear the board.
    

Example 3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

    
    
    Input: board = "G", hand = "GGGGG"
    Output: 2
    Explanation: To make the board empty:
    - Insert 'G' so the board becomes G _G_.
    - Insert 'G' so the board becomes GG _G_. _GGG_ -> empty.
    2 balls from your hand were needed to clear the board.
    

Constraints

  • 1 <= board.length <= 16
  • 1 <= hand.length <= 5
  • board and hand consist of the characters 'R', 'Y', 'B', 'G', and 'W'.
  • The initial row of balls on the board will not have any groups of three or more consecutive balls of the same color.

Solution

Method 1 – DFS with Memoization

Intuition

The key idea is to try every possible way to insert a ball from the hand into the board and recursively clear the board, using memoization to avoid recomputation. We simulate the process, always removing groups of three or more consecutive balls of the same color after each insertion, and keep track of the minimum number of insertions needed.

Approach

  1. Use a recursive function with memoization to represent the state as (board, hand).
  2. For each position in the board, try inserting each color from the hand.
  3. After insertion, repeatedly remove any group of three or more consecutive balls of the same color.
  4. Recurse on the new board and updated hand.
  5. If the board is empty, return the number of insertions used.
  6. If the hand is empty and the board is not, return -1.
  7. Track the minimum insertions across all possible moves.
  8. Use a dictionary to count balls in hand and a cache for memoization.

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
public:
    int findMinStep(string board, string hand) {
        unordered_map<char, int> cnt;
        for (char c : hand) cnt[c]++;
        unordered_map<string, int> memo;
        int ans = dfs(board, cnt, memo);
        return ans == INT_MAX ? -1 : ans;
    }
private:
    int dfs(string board, unordered_map<char, int>& cnt, unordered_map<string, int>& memo) {
        board = shrink(board);
        if (board.empty()) return 0;
        string key = board + "#";
        for (auto& [c, v] : cnt) key += c + to_string(v);
        if (memo.count(key)) return memo[key];
        int ans = INT_MAX;
        for (int i = 0; i <= board.size(); ++i) {
            for (auto& [c, v] : cnt) {
                if (v == 0) continue;
                if (i > 0 && board[i-1] == c) continue;
                cnt[c]--;
                string nb = board.substr(0, i) + c + board.substr(i);
                int res = dfs(nb, cnt, memo);
                if (res != INT_MAX) ans = min(ans, 1 + res);
                cnt[c]++;
            }
        }
        return memo[key] = ans;
    }
    string shrink(string s) {
        int n = s.size();
        for (int i = 0, j; i < n; ) {
            j = i;
            while (j < n && s[j] == s[i]) ++j;
            if (j - i >= 3) return shrink(s.substr(0, i) + s.substr(j));
            i = j;
        }
        return s;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
func findMinStep(board string, hand string) int {
    cnt := make(map[byte]int)
    for i := 0; i < len(hand); i++ {
        cnt[hand[i]]++
    }
    memo := make(map[string]int)
    var shrink func(string) string
    shrink = func(s string) string {
        for i := 0; i < len(s); {
            j := i
            for j < len(s) && s[j] == s[i] {
                j++
            }
            if j-i >= 3 {
                return shrink(s[:i] + s[j:])
            }
            i = j
        }
        return s
    }
    var dfs func(string, map[byte]int) int
    dfs = func(b string, c map[byte]int) int {
        b = shrink(b)
        if len(b) == 0 {
            return 0
        }
        key := b + "#"
        for k, v := range c {
            key += string(k) + string(v+'0')
        }
        if v, ok := memo[key]; ok {
            return v
        }
        ans := 1<<30
        for i := 0; i <= len(b); i++ {
            for k, v := range c {
                if v == 0 {
                    continue
                }
                if i > 0 && b[i-1] == k {
                    continue
                }
                c[k]--
                nb := b[:i] + string(k) + b[i:]
                res := dfs(nb, c)
                if res != 1<<30 {
                    if 1+res < ans {
                        ans = 1 + res
                    }
                }
                c[k]++
            }
        }
        memo[key] = ans
        return ans
    }
    ans := dfs(board, cnt)
    if ans == 1<<30 {
        return -1
    }
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
    public int findMinStep(String board, String hand) {
        Map<Character, Integer> cnt = new HashMap<>();
        for (char c : hand.toCharArray()) cnt.put(c, cnt.getOrDefault(c, 0) + 1);
        Map<String, Integer> memo = new HashMap<>();
        int ans = dfs(board, cnt, memo);
        return ans == Integer.MAX_VALUE ? -1 : ans;
    }
    private int dfs(String board, Map<Character, Integer> cnt, Map<String, Integer> memo) {
        board = shrink(board);
        if (board.length() == 0) return 0;
        StringBuilder key = new StringBuilder(board).append("#");
        for (Map.Entry<Character, Integer> e : cnt.entrySet()) key.append(e.getKey()).append(e.getValue());
        if (memo.containsKey(key.toString())) return memo.get(key.toString());
        int ans = Integer.MAX_VALUE;
        for (int i = 0; i <= board.length(); ++i) {
            for (Map.Entry<Character, Integer> e : cnt.entrySet()) {
                char c = e.getKey();
                int v = e.getValue();
                if (v == 0) continue;
                if (i > 0 && board.charAt(i-1) == c) continue;
                cnt.put(c, v-1);
                String nb = board.substring(0, i) + c + board.substring(i);
                int res = dfs(nb, cnt, memo);
                if (res != Integer.MAX_VALUE) ans = Math.min(ans, 1 + res);
                cnt.put(c, v);
            }
        }
        memo.put(key.toString(), ans);
        return ans;
    }
    private String shrink(String s) {
        for (int i = 0, n = s.length(); i < n; ) {
            int j = i;
            while (j < n && s.charAt(j) == s.charAt(i)) ++j;
            if (j - i >= 3) return shrink(s.substring(0, i) + s.substring(j));
            i = j;
        }
        return s;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
    fun findMinStep(board: String, hand: String): Int {
        val cnt = hand.groupingBy { it }.eachCount().toMutableMap()
        val memo = mutableMapOf<String, Int>()
        fun shrink(s: String): String {
            var i = 0
            while (i < s.length) {
                var j = i
                while (j < s.length && s[j] == s[i]) j++
                if (j - i >= 3) return shrink(s.substring(0, i) + s.substring(j))
                i = j
            }
            return s
        }
        fun dfs(b: String, c: MutableMap<Char, Int>): Int {
            val sb = StringBuilder(b)
            val key = sb.append("#").apply {
                c.forEach { (k, v) -> append(k).append(v) }
            }.toString()
            if (memo.containsKey(key)) return memo[key]!!
            val nb = shrink(b)
            if (nb.isEmpty()) return 0
            var ans = Int.MAX_VALUE
            for (i in 0..nb.length) {
                for ((k, v) in c) {
                    if (v == 0) continue
                    if (i > 0 && nb[i-1] == k) continue
                    c[k] = v - 1
                    val res = dfs(nb.substring(0, i) + k + nb.substring(i), c)
                    if (res != Int.MAX_VALUE) ans = minOf(ans, 1 + res)
                    c[k] = v
                }
            }
            memo[key] = ans
            return ans
        }
        val ans = dfs(board, cnt)
        return if (ans == Int.MAX_VALUE) -1 else ans
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution:
    def findMinStep(self, board: str, hand: str) -> int:
        from collections import Counter
        from functools import lru_cache
        cnt = Counter(hand)
        def shrink(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:
                    return shrink(s[:i] + s[j:])
                i = j
            return s
        memo = {}
        def dfs(b: str, c: tuple) -> int:
            b = shrink(b)
            if not b:
                return 0
            key = (b, c)
            if key in memo:
                return memo[key]
            ans = float('inf')
            c_dict = dict(zip('RYBGW', c))
            for i in range(len(b)+1):
                for idx, k in enumerate('RYBGW'):
                    if c_dict[k] == 0:
                        continue
                    if i > 0 and b[i-1] == k:
                        continue
                    c_dict[k] -= 1
                    nc = tuple(c_dict[x] for x in 'RYBGW')
                    res = dfs(b[:i]+k+b[i:], nc)
                    if res != float('inf'):
                        ans = min(ans, 1+res)
                    c_dict[k] += 1
            memo[key] = ans
            return ans
        c_tuple = tuple(cnt.get(x, 0) for x in 'RYBGW')
        ans = dfs(board, c_tuple)
        return -1 if ans == float('inf') else ans
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
use std::collections::HashMap;
impl Solution {
    pub fn find_min_step(board: String, hand: String) -> i32 {
        fn shrink(s: &str) -> String {
            let s = s.as_bytes();
            let mut i = 0;
            while i < s.len() {
                let mut j = i;
                while j < s.len() && s[j] == s[i] { j += 1; }
                if j - i >= 3 {
                    let mut ns = Vec::new();
                    ns.extend_from_slice(&s[..i]);
                    ns.extend_from_slice(&s[j..]);
                    return shrink(&String::from_utf8(ns).unwrap());
                }
                i = j;
            }
            String::from_utf8(s.to_vec()).unwrap()
        }
        fn dfs(b: String, c: &mut [i32; 5], memo: &mut HashMap<(String, [i32; 5]), i32>) -> i32 {
            let b = shrink(&b);
            if b.is_empty() { return 0; }
            if let Some(&v) = memo.get(&(b.clone(), *c)) { return v; }
            let mut ans = i32::MAX;
            for i in 0..=b.len() {
                for (idx, k) in b"RYBGW".iter().enumerate() {
                    if c[idx] == 0 { continue; }
                    if i > 0 && b.as_bytes()[i-1] == *k { continue; }
                    c[idx] -= 1;
                    let mut nb = b.clone();
                    nb.insert(i, *k as char);
                    let res = dfs(nb, c, memo);
                    if res != i32::MAX { ans = ans.min(1 + res); }
                    c[idx] += 1;
                }
            }
            memo.insert((b, *c), ans);
            ans
        }
        let mut c = [0; 5];
        for ch in hand.chars() {
            match ch {
                'R' => c[0] += 1,
                'Y' => c[1] += 1,
                'B' => c[2] += 1,
                'G' => c[3] += 1,
                'W' => c[4] += 1,
                _ => {}
            }
        }
        let mut memo = HashMap::new();
        let ans = dfs(board, &mut c, &mut memo);
        if ans == i32::MAX { -1 } else { ans }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
    findMinStep(board: string, hand: string): number {
        const cnt: Record<string, number> = {};
        for (const c of hand) cnt[c] = (cnt[c] || 0) + 1;
        const memo: Record<string, number> = {};
        function shrink(s: string): string {
            let i = 0;
            while (i < s.length) {
                let j = i;
                while (j < s.length && s[j] === s[i]) j++;
                if (j - i >= 3) return shrink(s.slice(0, i) + s.slice(j));
                i = j;
            }
            return s;
        }
        function dfs(b: string, c: Record<string, number>): number {
            b = shrink(b);
            if (!b) return 0;
            let key = b + '#';
            for (const k of Object.keys(c)) key += k + c[k];
            if (key in memo) return memo[key];
            let ans = Infinity;
            for (let i = 0; i <= b.length; i++) {
                for (const k of Object.keys(c)) {
                    if (c[k] === 0) continue;
                    if (i > 0 && b[i-1] === k) continue;
                    c[k]--;
                    const res = dfs(b.slice(0, i) + k + b.slice(i), c);
                    if (res !== Infinity) ans = Math.min(ans, 1 + res);
                    c[k]++;
                }
            }
            memo[key] = ans;
            return ans;
        }
        const ans = dfs(board, { ...cnt });
        return ans === Infinity ? -1 : ans;
    }
}

Complexity

  • ⏰ Time complexity: O(2^(n+m) * n^2) — For each state (board, hand), we try all insertions and all hand balls, and the board can shrink recursively. The number of unique states is exponential in board and hand size, but memoization prunes repeated work.
  • 🧺 Space complexity: O(2^(n+m) * n) — For memoization and recursion stack, where n is the board length and m is the hand length.