Formatted question description: https://leetcode.ca/all/488.html
488. Zuma Game
Level
Hard
Description
Think about Zuma Game. You have a row of balls on the table, colored red(R), yellow(Y), blue(B), green(G), and white(W). You also have several balls in your hand.
Each time, you may choose a ball in your hand, and insert it into the row (including the leftmost place and rightmost place). Then, if there is a group of 3 or more balls in the same color touching, remove these balls. Keep doing this until no more balls can be removed.
Find the minimal balls you have to insert to remove all the balls on the table. If you cannot remove all the balls, output 1.
Example 1:
Input: board = “WRRBBW”, hand = “RB”
Output: 1
Explanation: WRRBBW > WRR[R]BBW > WBBW > WBB[B]W > WW
Example 2:
Input: board = “WWRRBBWW”, hand = “WRBRW”
Output: 2
Explanation: WWRRBBWW > WWRR[R]BBWW > WWBBWW > WWBB[B]WW > WWWW > empty
Example 3:
Input: board = “G”, hand = “GGGGG”
Output: 2
Explanation: G > G[G] > GG[G] > empty
Example 4:
Input: board = “RBYYBBRRB”, hand = “YRBGB”
Output: 3
Explanation: RBYYBBRRB > RBYY[Y]BBRRB > RBBBRRB > RRRB > B > B[B] > BB[B] > empty
Constraints:
 You may assume that the initial row of balls on the table wont have any 3 or more consecutive balls with the same color.
 The number of balls on the table won’t exceed 16, and the string represents these balls is called “board” in the input.
 The number of balls in your hand won’t exceed 5, and the string represents these balls is called “hand” in the input.
 Both input strings will be nonempty and only contain characters ‘R’,’Y’,’B’,’G’,’W’.
Solution
Use backtrack with memorization. First loop over hand
to obtain the number of balls of each color in hand
. Then call the backtrack method.
In the backtrack method, first remove three or more consecutive balls with the same color from board
. The remove operation is executed several times over board
until no more balls can be removed. Then return 0 if board
is empty. Use board
concatenated with the string representation of each color’s count as the key to loop up the memorization. If the key exists, obtain the number of insertions from the key and return.
For each available color in hand
, try each insertion position in board
and obtain the minimum number of insertions, and update the memorization. If the empty string is reached, then it is possible to remove all the balls, and return the minimum number of insertions. Otherwise, return 1.

class Solution { public int findMinStep(String board, String hand) { Map<Character, Integer> colorIdMap = new HashMap<Character, Integer>(); Map<Integer, Character> idColorMap = new HashMap<Integer, Character>(); String colors = "RYBGW"; for (int id = 0; id < 5; id++) { char color = colors.charAt(id); colorIdMap.put(color, id); idColorMap.put(id, color); } Map<String, Integer> memo = new HashMap<String, Integer>(); int[] hands = new int[5]; int handLength = hand.length(); for (int i = 0; i < handLength; i++) { char c = hand.charAt(i); int index = colorIdMap.get(c); hands[index]++; } return backtrack(board, hands, idColorMap, memo); } public int backtrack(String board, int[] hands, Map<Integer, Character> idColorMap, Map<String, Integer> memo) { board = removeSame(board); if (board.length() == 0) return 0; String key = board + Arrays.toString(hands); if (memo.containsKey(key)) return memo.get(key); int insertions = 1; int handsLength = hands.length, boardLength = board.length(); for (int i = 0; i < handsLength; i++) { if (hands[i] != 0) { char color = idColorMap.get(i); hands[i]; for (int j = 0; j <= boardLength; j++) { String newBoard = board.substring(0, j) + color + board.substring(j); int nextInsertions = backtrack(newBoard, hands, idColorMap, memo); if (nextInsertions != 1) { if (insertions == 1) insertions = nextInsertions + 1; else insertions = Math.min(insertions, nextInsertions + 1); } } hands[i]++; } } memo.put(key, insertions); return insertions; } public String removeSame(String board) { StringBuffer sb = new StringBuffer(board); int length = sb.length(); boolean flag = true; while (flag) { int left = length  2, right = length  1; while (left >= 0) { if (sb.charAt(left) == sb.charAt(right)) left; else { if (right  left >= 3) { sb.delete(left + 1, right + 1); right = left; left; } else right = left; } } if (right  left >= 3) sb.delete(left + 1, right + 1); int curLength = sb.length(); if (curLength == length) flag = false; else length = curLength; } return sb.toString(); } }

Todo

class Solution(object): def findMinStep(self, board, hand): """ :type board: str :type hand: str :rtype: int """ def dfs(line, balls, visited): line = reduceLine(line) if (line, balls) in visited: return visited[line, balls] if len(line) == 0: return len(hand)  len(balls) if len(balls) == 0: return float("inf") res = float("inf") for i in range(len(balls)): for j in range(len(line) + 1): if j == 0 and line[0] != balls[i]: continue elif j == len(line) and line[1] != balls[i]: continue elif 0 < j < len(line) and balls[i] != line[j  1] and balls[i] != line[j]: continue res = min(res, dfs(line[:j] + balls[i] + line[j:], balls[:i] + balls[i + 1:], visited)) visited[line, balls] = res return res def reduceLine(line): def reducer(line): if len(line) < 3: return line ret = [] dp = [1] * len(line) pre = line[1] count = 1 for i in reversed(range(len(line)  1)): if line[i] == pre: count += 1 else: pre = line[i] count = 1 dp[i] = count i = 0 while i < len(line): if dp[i] >= 3: i += dp[i] else: ret.extend(line[i:i + dp[i]]) i += dp[i] return "".join(ret) if len(line) < 3: return line ans = line for _ in range(len(line) / 3): ans = reducer(ans) return ans visited = {} ret = dfs(board, "".join(sorted(hand)), visited) return ret if ret != float("inf") else 1