Leetcode 488. Zuma Game
Problem Explanation
In this problem, you are given a Zuma game setup where you have a row of balls with different colors and a hand of balls also with different colors. The game is such that you are to insert a ball from your hand into the row at any location. If there is a group of 3 or more balls of the same color together after you insert a ball, the grouped balls are removed automatically. And you keep doing this until no more balls can be removed or all balls on the tabletop are removed.
Our objective is to find the minimum number of balls that need to be inserted to remove all the balls from the table. If it is not possible to remove all the balls from the table, then we output -1.
Constraints and assumptions:
- The initial row of balls on the table wonโt have any 3 or more consecutive balls with the same color.
- The number of balls on the table won't exceed 20.
- The number of balls in your hand won't exceed 5.
- Both input strings will be non-empty and only contain characters 'R','Y','B','G','W'.
Walkthrough an example
Let's consider the example where we have "WWRRBBWW" as the board and "WRBRW" as the hand.
The approach is to try inserting the balls from our hand to the board one by one and recursively solve the game for the updated board.
-
Original board is "WWRRBBWW", and the hand is "WRBRW".
-
We insert 'W' from our hand at index 4.
- The board becomes "WWRRWBBWW", and the hand becomes "RBRW".
- After removing the 3 consecutive 'W' at index 2, 3 and 4. The board becomes "WWBBWW".
-
We recursively solve for "WWBBWW" and "RBRW".
- We insert 'B' from our hand at index 4.
- The board becomes "WWBBBWW", and the hand becomes "RRW".
- After removing the 3 consecutive 'B' at index 2, 3 and 4. The board becomes "WWWW".
-
We recursively solve for "WWWW" and "RRW".
- Since all the balls are the same color and none are in the hand, no more steps are needed and we stop.
So, in this case, the minimum number of balls to insert is 2, which is the total number of balls we inserted: 'W' and 'B'.
Approach
We are using a Depth-first search (DFS) algorithm here, where the board is explored recursively after inserting each ball from the hand. The game state after each insertion is stored in a memo dictionary to avoid duplicate calculations, this is basically dynamic programming. The number of balls needed to clear the board is returned from the recursive calls, and the minimum of these is kept as the answer.
Also, an important function is the deDup function which removes any group of 3 or more balls of the same color from the board. This function is used after each insertion to clean up the group of same-colored balls.
Pseudo Code
1 2 3function findMinStep(board, hand) 4 Initialize a memo as an empty dictionary 5 Call dfs on board + "#" and hand, with memo as a parameter 6 If the result is INT_MAX , return -1 7 return result 8 9function dfs(board, hand, memo) 10 Generate a hashkey from board + '#' + hand 11 If hashkey is in memo, return its value from memo 12 Call deDup on board 13 If board is "#" (empty), return 0 14 Remove balls from hand that are not present in board 15 If hand is empty, return INT_MAX (Infeasible) 16 Initialize answer as INT_MAX 17 For each ball in the board 18 For each ball in the hand 19 Call dfs on newBoard and newHand, with memo as parameter 20 Update answer as the minimum of answer and 1 + result 21 Update memo[hashKey] with answer 22 Return answer 23 24function deDup(board) 25 For each group of 3 or more same colored balls in board 26 Remove the group 27 Return board
Solution in Python
1 2python 3class Solution: 4 def findMinStep(self, board: str, hand: str) -> int: 5 freq = collections.Counter(hand) 6 def dfs(board): 7 board = self.removeConsecutive(board) 8 if not board: return 0 9 i, res = 0, float('inf') 10 for j in range(len(board)): 11 if board[i] == board[j]: continue 12 need = 3 - (j - i) 13 if freq[board[i]] >= need: 14 freq[board[i]] -= need 15 res = min(res, need + dfs(board[:i] + board[j:])) 16 freq[board[i]] += need 17 i = j 18 return res 19 res = dfs(board) 20 return res if res != float('inf') else -1 21 22 def removeConsecutive(self, board): 23 while True: 24 i = 0 25 for j in range(len(board)): 26 if board[i] == board[j]: continue 27 if j - i >= 3: break 28 i = j 29 if j == len(board) and j - i < 3: return board 30 board = board[:i] + board[j:]
The main function findMinStep uses a helper function dfs which is recursively called for each state of the board after adding a ball from the hand. The helper function removeConsecutive is used to remove any group of 3 or more balls of the same color.
Solution in Java
1 2java 3class Solution { 4 public int findMinStep(String board, String hand) { 5 int[] handCount = new int[26]; 6 for (int i = 0; i < hand.length(); ++i) ++handCount[hand.charAt(i) - 'A']; 7 int rs = helper(board + "#", handCount); // append a "#" to avoid special process while last few balls. 8 return rs == Integer.MAX_VALUE ? -1 : rs; 9 } 10 11 private int helper(String s, int[] h) { 12 s = removeConsecutive(s); 13 if (s.equals("#")) return 0; 14 int rs = Integer.MAX_VALUE, need = 0; 15 for (int i = 0, j = 0 ; j < s.length(); ++j) { 16 if (s.charAt(j) == s.charAt(i)) continue; 17 need = 3 - (j - i); //balls need to remove current consecutive balls. 18 if (h[s.charAt(i) - 'A'] >= need) { 19 h[s.charAt(i) - 'A'] -= need; 20 rs = Math.min(rs, need + helper(s.substring(0, i) + s.substring(j), h)); 21 h[s.charAt(i) - 'A'] += need; 22 } 23 i = j; 24 } 25 return rs; 26 } 27 28 //remove consecutive balls longer than 3 29 private String removeConsecutive(String board) { 30 for (int i = 0, j = 0; j < board.length(); ++j) { 31 if (board.charAt(j) == board.charAt(i)) continue; 32 if (j - i >= 3) return removeConsecutive(board.substring(0, i) + board.substring(j)); 33 else i = j; 34 } 35 return board; 36 } 37}
In Java Solution, we use a character array handCount to store the count of each ball in the hand. As Java does not support changing strings in-place, we use substring method to simulate the process of changing the state of the board. The helper function helps to simulate the game and recursively return the minimum balls required after each insertion.
Solution in JavaScript
1 2javascript 3var findMinStep = function(board, hand) { 4 let map = new Map(); 5 for(let c of hand){ 6 if(map.has(c)) map.set(c, map.get(c) + 1); 7 else map.set(c, 1); 8 } 9 let res = helper(board, map); 10 return res === Infinity ? -1 : res; 11}; 12 13var helper = function(board, map){ 14 if(board === "") return 0; 15 let i = 0, res = Infinity; 16 while(i < board.length){ 17 let j = i; 18 while(j < board.length && board[j] === board[i]) j++; 19 let need = Math.max(0, 3 - (j - i)); 20 if(map.has(board[i]) && map.get(board[i]) >= need){ 21 let count = map.get(board[i]); 22 map.set(board[i], count - need); 23 let boardNew = updateBoard(board.substring(0, i) + board.substring(j)); 24 res = Math.min(res, helper(boardNew, map) + need); 25 map.set(board[i], count); 26 } 27 i = j; 28 } 29 return res; 30}; 31 32var updateBoard = function(board){ 33 let i = 0; 34 while(i < board.length){ 35 let j = i; 36 while(j < board.length && board[j] === board[i]) j++; 37 if(j - i >= 3) return updateBoard(board.substring(0, i) + board.substring(j)); 38 else i = j; 39 } 40 return board; 41};
In JavaScript Solution, we use a map data structure to store the count of each ball in the hand. The helper function helps to recursively simulate the game and return the minimum balls required after each insertion. The updateBoard function is used to remove any group of 3 or more balls of the same color from the board.Each of these solutions revolves around the same core ideas. The Python, Java, and JavaScript approaches each use a recursive depth-first search (DFS) method to explore all possible states of the game. The number of remaining balls in the hand and on the board is tracked throughout the game.
In each iteration of the recursion, every combination of placing a color from the hand is tried. After placing the ball, a function is called to remove all possible groups of 3 or more balls of the same color. The state of the board after doing this is then passed in the next recursive call.
The DFS explores the complete game space. However, this can be optimized by memoization, which ensures that a gameboard state is not processed more than once. The hand state does not need to be memoed because it is always in a decreasing order.
Furthermore, before going into another recursion step, check if it is even possible to go into this direction by seeing if the balls in the hand can cover the needs of the current board.
Lastly, a point to note, is that the implementation of the algorithm can be more efficient in an interpreted language like Python or JavaScript due to the language features, like list splicing and easy string manipulations, compared to Java that is compiled and does not permit changing strings in place which necessitates the substring method to simulate changing the state of the board.
That being said, this problem is a good example of how different languages can utilize similar logic applied in slightly different implementations due to variations in language syntax and capabilities. Such tasks also best demonstrate the importance of understanding fundamental algorithms and data structures since they can be applied in any programming language.
Got a question?ย Ask the Teaching Assistantย anything you don't understand.
Still not clear? Ask in the Forum, ย Discordย orย Submitย the part you don't understand to our editors.