10
// add an extra character, because .split() throws away trailing empty strings11
String splitter = hand + "x";12
red = splitter.split("R").length - 1;13
yellow = splitter.split("Y").length - 1;14
green = splitter.split("G").length - 1;15
blue = splitter.split("B").length - 1;16
white = splitter.split("W").length - 1;28
return red == 0 && yellow == 0 && green == 0 && blue == 0 && white == 0;31
List<String> colors() {32
List<String> res = new ArrayList<>();33
if (red > 0) res.add("R");34
if (yellow > 0) res.add("Y");35
if (green > 0) res.add("G");36
if (blue > 0) res.add("B");37
if (white > 0) res.add("W");41
void removeColor(String color) {61
public StringBuilder buildStringWithColon() {62
return new StringBuilder()76
/** key = hand + ":" + board */77
private final Map<String, Integer> boardHandToMinStep = new HashMap<>();80
* store hand in a custom object; eases work and memoization (handles equivalency of reordered81
* hand) for each color in your hand: try to insert the color in each *effective* location -82
* effective location means "one preceding a same-color set of balls"; in other words: "a location83
* to the left of a same-color ball AND NOT to the right of a same-color ball" resolve the board84
* if inserting to that location finishes the game, return 1 otherwise, recur to the resulting85
* hand minstep for this setup == minimum of all resulting hands + 1 memoize this minstep, then88
public int findMinStep(String board, String hand) {89
// store hand in a custom object; eases work and memoization (handles equivalency of reordered91
Hand h = new Hand(hand);92
return findMinStep(board, h, 9999);95
private int findMinStep(String board, Hand hand, int remainingDepth) {96
// resolve board, i.e. remove triples and higher97
board = resolve(board);98
final String key = hand.buildStringWithColon().append(board).toString();99
if (board.length() == 0) {101
} else if (boardHandToMinStep.containsKey(key)) {102
return boardHandToMinStep.get(key);105
// OPTIMIZATION #3 - reduced time by 25%106
// don't go deeper than the deepest known solution - 1107
if (remainingDepth <= 0108
// OPTIMIZATION #2 - lowered from 1min to 4sec reduced time by 93%109
// for each color in the board, if there are ever fewer than three of that color in the110
// board and hand combined, fast fail111
|| !canWin(board, hand)) {112
boardHandToMinStep.put(key, -1);117
// for each color in your hand:118
for (String color : hand.colors()) {119
// Store a new "next hand" and remove the color120
Hand nextHand = new Hand(hand);121
nextHand.removeColor(color);122
// for each *effective* insert location123
// - effective location means "one preceding same-color ball(s)"; in other words: "a location124
// to the left of a same-color ball AND NOT to the right of a same-color ball"125
for (int loc : effectiveLocations(color, board, nextHand.isEmpty())) {126
// insert the color and store as "next board"127
String nextBoard = board.substring(0, loc) + color + board.substring(loc);128
// recur to the resulting hand130
findMinStep(nextBoard, nextHand, minStep == -1 ? remainingDepth - 1 : minStep - 2);131
if (childMinStep != -1) {132
// minstep for this setup == minimum of all resulting hands + 1133
minStep = minStep == -1 ? (1 + childMinStep) : Math.min(minStep, 1 + childMinStep);137
// memoize this minstep, then return it138
boardHandToMinStep.put(key, minStep);142
private boolean canWin(String board, Hand hand) {143
String splitter = board + "x";144
int red = splitter.split("R").length - 1;145
int yellow = splitter.split("Y").length - 1;146
int green = splitter.split("G").length - 1;147
int blue = splitter.split("B").length - 1;148
int white = splitter.split("W").length - 1;150
return (red == 0 || red + hand.red > 2)151
&& (yellow == 0 || yellow + hand.yellow > 2)152
&& (green == 0 || green + hand.green > 2)153
&& (blue == 0 || blue + hand.blue > 2)154
&& (white == 0 || white + hand.white > 2);158
* effective location means "one preceding a same-color set of 1 or more balls"; in other words:159
* "a location to the left of a same-color ball AND NOT to the right of a same-color ball" ^^ The160
* above first pass is incorrect. Sometimes balls have to interrupt other colors to prevent early163
* <p>effective location means "all locations except after a same-color ball"165
private List<Integer> effectiveLocations(String color, String board, boolean isLastInHand) {166
List<Integer> res = new ArrayList<>();168
// OPTIMIZATION #4 - prefer greedy locations by adding them in this order: - reduced time by 93%169
// - preceding 2 of the same color170
// - preceding exactly 1 of the same color171
// - neighboring 0 of the same color172
List<Integer> greedy2 = new ArrayList<>();173
List<Integer> greedy3 = new ArrayList<>();175
// Preceding 2 of the same color:176
for (int i = 0; i <= board.length(); i++) {177
if (i < board.length() - 1 && board.substring(i, i + 2).equals(color + color)) {179
// skip the next 2 locations; they would be part of the same consecutive set of "this" color181
} else if (i < board.length() && board.substring(i, i + 1).equals(color)) {183
// skip the next 1 location; it would be part of the same consecutive set of "this" color186
// OPTIMIZATION #5 - if a ball is not next to one of the same color, it must be between two187
// identical, of a different color - 10s to .8s190
&& board.length() > i191
&& board.substring(i - 1, i).equals(board.substring(i, i + 1))) {196
// OPTIMIZATION #1 - reduced time by 90%197
// if this is the last one in the hand, then it MUST be added to 2 others of the same color206
/** repeatedly collapse sets of 3 or more */207
private String resolve(String board) {209
while (!board.equals(copy)) {212
board = copy.replaceFirst("(.)\\1\\1+", "");