🧠
Brute Force (Pure Recursion with Backtracking)
💡 This approach explores all possible insertions and removals without caching results. It is the foundation to understand the problem's complexity and why optimization is necessary.
Intuition
Try inserting each ball from the hand into every possible position on the board, then remove consecutive balls recursively. Repeat until the board is empty or no moves remain.
Algorithm
- If the board is empty, return 0 moves needed.
- For each position in the board, try inserting each ball from the hand.
- After insertion, remove consecutive balls recursively.
- Recurse on the new board and updated hand to find minimal moves.
💡 The recursion and removal steps are tricky because removals cascade and the state space grows exponentially.
Recurrence:f(board, hand) = min over all insertions { 1 + f(new_board_after_removal, new_hand) }
from collections import Counter
def findMinStep(board: str, hand: str) -> int:
hand_count = Counter(hand)
def remove_consecutive(s):
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
memo = {}
def dfs(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')
for i in range(len(s) + 1):
for c in hand_count:
if hand_count[c] == 0:
continue
new_s = s[:i] + c + s[i:]
new_s = remove_consecutive(new_s)
hand_count[c] -= 1
temp = dfs(new_s, hand_count)
if temp != -1:
res = min(res, 1 + temp)
hand_count[c] += 1
memo[key] = -1 if res == float('inf') else res
return memo[key]
return dfs(board, hand_count)
# Example usage:
if __name__ == '__main__':
print(findMinStep("WRRBBW", "RB")) # Output: -1
print(findMinStep("WWRRBBWW", "WRBRW")) # Output: 2
Line Notes
hand_count = Counter(hand)Count balls in hand to track availability efficiently
def remove_consecutive(s):Helper to remove consecutive balls recursively
while j < len(s) and s[j] == s[i]:Find consecutive balls of the same color
if j - i >= 3:Remove group if size >= 3 to trigger chain reaction
key = (s, tuple(sorted(hand_count.items())))Memoization key to avoid recomputation of same state
for i in range(len(s) + 1):Try inserting ball at every possible position
for c in hand_count:Try every ball color available in hand
hand_count[c] -= 1Use one ball of color c from hand
memo[key] = -1 if res == float('inf') else resStore result or -1 if no solution
import java.util.*;
public class ZumaGame {
private Map<String, Integer> memo = new HashMap<>();
public int findMinStep(String board, String hand) {
int[] handCount = new int[26];
for (char c : hand.toCharArray()) handCount[c - 'A']++;
int res = dfs(board, handCount);
return res == Integer.MAX_VALUE ? -1 : res;
}
private int dfs(String board, int[] handCount) {
if (board.length() == 0) return 0;
String key = board + Arrays.toString(handCount);
if (memo.containsKey(key)) return memo.get(key);
int res = Integer.MAX_VALUE;
for (int i = 0; i <= board.length(); i++) {
for (int c = 0; c < 26; c++) {
if (handCount[c] == 0) continue;
char ch = (char) (c + 'A');
String newBoard = removeConsecutive(board.substring(0, i) + ch + board.substring(i));
handCount[c]--;
int temp = dfs(newBoard, handCount);
if (temp != Integer.MAX_VALUE) res = Math.min(res, 1 + temp);
handCount[c]++;
}
}
memo.put(key, res);
return res;
}
private String removeConsecutive(String s) {
int i = 0;
while (i < s.length()) {
int j = i;
while (j < s.length() && s.charAt(j) == s.charAt(i)) j++;
if (j - i >= 3) {
s = s.substring(0, i) + s.substring(j);
i = 0;
} else {
i++;
}
}
return s;
}
public static void main(String[] args) {
ZumaGame zg = new ZumaGame();
System.out.println(zg.findMinStep("WRRBBW", "RB")); // -1
System.out.println(zg.findMinStep("WWRRBBWW", "WRBRW")); // 2
}
}
Line Notes
private Map<String, Integer> memo = new HashMap<>();Memoize results to avoid recomputation
int[] handCount = new int[26];Count balls in hand by character index
for (int i = 0; i <= board.length(); i++) {Try inserting at every position
if (handCount[c] == 0) continue;Skip colors not available in hand
String newBoard = removeConsecutive(...)Remove consecutive balls after insertion
memo.put(key, res);Store computed result for current state
#include <bits/stdc++.h>
using namespace std;
class Solution {
unordered_map<string, int> memo;
string removeConsecutive(string s) {
int i = 0;
while (i < (int)s.size()) {
int j = i;
while (j < (int)s.size() && s[j] == s[i]) j++;
if (j - i >= 3) {
s = s.substr(0, i) + s.substr(j);
i = 0;
} else {
i++;
}
}
return s;
}
int dfs(string board, vector<int>& handCount) {
if (board.empty()) return 0;
string key = board + "#";
for (int c : handCount) key += to_string(c) + ",";
if (memo.count(key)) return memo[key];
int res = INT_MAX;
for (int i = 0; i <= (int)board.size(); i++) {
for (int c = 0; c < 26; c++) {
if (handCount[c] == 0) continue;
char ch = 'A' + c;
string newBoard = board.substr(0, i) + ch + board.substr(i);
newBoard = removeConsecutive(newBoard);
handCount[c]--;
int temp = dfs(newBoard, handCount);
if (temp != INT_MAX) res = min(res, 1 + temp);
handCount[c]++;
}
}
return memo[key] = res;
}
public:
int findMinStep(string board, string hand) {
vector<int> handCount(26, 0);
for (char c : hand) handCount[c - 'A']++;
int res = dfs(board, handCount);
return res == INT_MAX ? -1 : res;
}
};
int main() {
Solution sol;
cout << sol.findMinStep("WRRBBW", "RB") << "\n"; // -1
cout << sol.findMinStep("WWRRBBWW", "WRBRW") << "\n"; // 2
return 0;
}
Line Notes
unordered_map<string, int> memo;Memoize states to prune recursion
while (j < (int)s.size() && s[j] == s[i]) j++;Find consecutive balls for removal
string key = board + "#";Create unique key for memo including hand counts
for (int c = 0; c < 26; c++) {Try all ball colors in hand
handCount[c]--Use one ball of color c
return memo[key] = res;Cache and return minimal moves
var findMinStep = function(board, hand) {
const handCount = {};
for (const c of hand) handCount[c] = (handCount[c] || 0) + 1;
function removeConsecutive(s) {
let i = 0;
while (i < s.length) {
let j = i;
while (j < s.length && s[j] === s[i]) j++;
if (j - i >= 3) {
s = s.slice(0, i) + s.slice(j);
i = 0;
} else {
i++;
}
}
return s;
}
const memo = new Map();
function dfs(s, handCount) {
if (s.length === 0) return 0;
const key = s + JSON.stringify(handCount);
if (memo.has(key)) return memo.get(key);
let res = Infinity;
for (let i = 0; i <= s.length; i++) {
for (const c in handCount) {
if (handCount[c] === 0) continue;
const newS = removeConsecutive(s.slice(0, i) + c + s.slice(i));
handCount[c]--;
const temp = dfs(newS, handCount);
if (temp !== -1) res = Math.min(res, 1 + temp);
handCount[c]++;
}
}
memo.set(key, res === Infinity ? -1 : res);
return memo.get(key);
}
return dfs(board, handCount);
};
// Example usage:
console.log(findMinStep("WRRBBW", "RB")); // -1
console.log(findMinStep("WWRRBBWW", "WRBRW")); // 2
Line Notes
const handCount = {};Count balls in hand for quick lookup
function removeConsecutive(s)Remove consecutive balls recursively
const key = s + JSON.stringify(handCount);Memo key combines board and hand state
for (let i = 0; i <= s.length; i++) {Try inserting at every position
handCount[c]--Use one ball of color c
memo.set(key, res === Infinity ? -1 : res);Cache result or -1 if no solution
TimeO((n + m) * 5^(n+m)) where n = board length, m = hand length
SpaceO((n + m) * 5^(n+m)) due to memoization and recursion stack
Each recursive call tries all insertions and colors, leading to exponential states
💡 For n=10 and m=5, this is roughly millions of calls, which is impractical without pruning.
Interview Verdict: TLE / Use only to introduce problem and motivate memoization
Shows why naive recursion is infeasible and sets stage for DP optimization.