2
* Finds Initial State (State consists of shopkeeper & box locations + # of boxMoves) Uses BFS/A*3
* Algorithm to visit valid transition states Note: The min heuristic here is # of boxMoves +4
* manHattanDistance between box & target locations10
private static int[][] DIRS = {17
/** State holds shopkeeper and box location, as well as how many times the box has been pushed */18
class State implements Comparable<State> {25
public State(int personRow, int personCol, int boxRow, int boxCol, int boxPushes) {26
this.personRow = personRow;27
this.personCol = personCol;30
this.boxPushes = boxPushes;33
// Override equals - used along with hashcode when we have visited HashSet34
public boolean equals(Object o) {35
State other = (State) o;36
return this.personRow == other.personRow37
&& this.personCol == other.personCol38
&& this.boxRow == other.boxRow39
&& this.boxCol == other.boxCol;42
// Override the hashCode - Note: it's okay for this to have collisions43
// But it won't due to the problem constraint that there is a bound on NxM dimensions44
public int hashCode() {45
return personRow * 10_000 + personCol * 1_000 + boxRow * 100 + boxCol;48
// Override to string method - helpful in debugging state.49
public String toString() {50
return "ShopKeeper:{row:"62
// Implement comparable interface such that we return the state that63
// has the possibility of lowest distance using box push count + Manhattan distance64
public int compareTo(State other) {65
int minDistanceThis = this.boxPushes + distanceToTarget(this);66
int minDistanceOther = other.boxPushes + distanceToTarget(other);67
return Integer.compare(minDistanceThis, minDistanceOther);71
// Calculates Manhattan distance72
private int distanceToTarget(State state) {73
int yDiff = Math.abs(state.boxCol - targetCol);74
int xDiff = Math.abs(state.boxRow - targetRow);79
* Given a state, compare box location to target location to determine if it is a solution state.81
private boolean isSolutionState(State state) {82
return state.boxRow == targetRow && state.boxCol == targetCol;86
* Given a state, finds all valid transition states. This is accomplished by moving the ShopKeeper87
* in all 4 directions and validate - Next ShopKeeper location is in bounds and is not a wall89
* <p>We have additional logic for when the next shopkeeper location is the box location: - Get90
* next box location, by pushing the same direction, again validate that the next box location is91
* in bounds, and is not a wall.93
* <p>If it's a valid transition, create the new state with the new shop keeper location and if94
* the box moved, the new box location (also increment the number of box moves).96
private List<State> getNeighbors(State state) {98
int personRow = state.personRow;99
int personCol = state.personCol;100
int boxRow = state.boxRow;101
int boxCol = state.boxCol;103
List<State> states = new ArrayList<>();104
for (int[] dir : DIRS) {105
int rowMove = dir[0];106
int colMove = dir[1];107
int personRowNew = personRow + rowMove;108
int personColNew = personCol + colMove;109
// Shopkeeper cannot move into wall or go out of bounds skip to next direction110
if (!inBounds(personRowNew, personColNew) || isWall(personRowNew, personColNew)) {113
// Whether or not person will collide with box114
boolean willPushBox = personRowNew == boxRow && personColNew == boxCol;117
int boxRowNew = boxRow + rowMove;118
int boxColNew = boxCol + colMove;119
// Validate box can be pushed - if so push box and add to neighbor states120
if (inBounds(boxRowNew, boxColNew) && !isWall(boxRowNew, boxColNew)) {122
new State(personRowNew, personColNew, boxRowNew, boxColNew, state.boxPushes + 1));125
// Shop keeper moved, but not box126
states.add(new State(personRowNew, personColNew, boxRow, boxCol, state.boxPushes));132
// Given row/col, return whether it is wall133
private boolean isWall(int row, int col) {134
char cell = grid[row][col];138
// Given row/col return whether is inBounds139
private boolean inBounds(int row, int col) {140
int rows = grid.length;141
int cols = grid[0].length;142
if (row < 0 || col < 0 || row > rows - 1 || col > cols - 1) {148
/** Returns initial state. Also finds and stores the target location. */149
private State getInitialState() {151
int shopKeeperRow = 0;152
int shopKeeperCol = 0;157
for (int r = 0; r < grid.length; r++) {158
char[] row = grid[r];159
for (int c = 0; c < row.length; c++) {160
char cell = grid[r][c];164
} else if (cell == 'B') {167
} else if (cell == 'S') {174
return new State(shopKeeperRow, shopKeeperCol, boxRow, boxCol, 0);177
public int minPushBox(char[][] grid) {180
State initialState = getInitialState();181
Queue<State> queue = new PriorityQueue<>();182
Set<State> visited = new HashSet<>();183
queue.offer(initialState);185
// Explore every state using BSF and keep track of the best solution186
while (!queue.isEmpty()) {187
State state = queue.poll();188
if (visited.contains(state)) {193
Note: the reason we can return the first solution state we find, is because we are194
using priority queue with minDistance heuristic which means the first solution we find195
is guaranteed to be the optimal solution - (A* Algorithm)197
if (isSolutionState(state)) {198
return state.boxPushes;200
for (State neighbor : getNeighbors(state)) {201
if (!visited.contains(neighbor)) {202
queue.offer(neighbor);207
// No solution - return -1