Problem: Minimum Moves to Move a Box to Their Target Location

A storekeeper is a game in which the player pushes boxes around in a warehouse
trying to get them to target locations.

The game is represented by an `m x n` grid of characters `grid` where each
element is a wall, floor, or box.

Your task is to move the box `'B'` to the target position `'T'` under the
following rules:

* The character `'S'` represents the player. The player can move up, down, left, right in `grid` if it is a floor (empty cell).
* The character `'.'` represents the floor which means a free cell to walk.
* The character `'#'` represents the wall which means an obstacle (impossible to walk there).
* There is only one box `'B'` and one target cell `'T'` in the `grid`.
* The box can be moved to an adjacent free cell by standing next to the box and then moving in the direction of the box. This is a push.
* The player cannot walk through the box.

Return the minimum number of pushes to move the box to the target. If there is
no way to reach the target, return `-1`.


Python solution:
import math
import itertools
import bisect
import collections
import string
import heapq
import functools
import sortedcontainers
from typing import List, Dict, Tuple, Iterator
from collections import deque

class Solution:
  def minPushBox(self, grid: List[List[str]]) -> int:
    for i in range(len(grid)):
      for j in range(len(grid[0])):
        if grid[i][j] == "T":
          target = (i,j)
        if grid[i][j] == "B":
          box = (i,j)
        if grid[i][j] == "S":
          person = (i,j)

    def valid(x,y):
      return 0<=x<len(grid) and 0<=y<len(grid[0]) and grid[x][y]!='#'

    def check(curr,dest,box):
      que = deque([curr])
      v = set()
      while que:
        pos = que.popleft()
        if pos == dest: 
          return True
        new_pos = [(pos[0]+1,pos[1]),(pos[0]-1,pos[1]),(pos[0],pos[1]+1),(pos[0],pos[1]-1)]
        for x,y in new_pos:
          if valid(x,y) and (x,y) not in v and (x,y)!=box:
            v.add((x,y))
            que.append((x,y))
      return False

    q = deque([(0,box,person)])
    vis = {box+person}
    while q :
      dist, box, person = q.popleft()
      if box == target:
        return dist

      b_coord = [(box[0]+1,box[1]),(box[0]-1,box[1]),(box[0],box[1]+1),(box[0],box[1]-1)]
      p_coord = [(box[0]-1,box[1]),(box[0]+1,box[1]),(box[0],box[1]-1),(box[0],box[1]+1)]

      for new_box,new_person in zip(b_coord,p_coord): 
        if valid(*new_box) and new_box+box not in vis:
          if valid(*new_person) and check(person,new_person,box):
            vis.add(new_box+box)
            q.append((dist+1,new_box,box))

    return -1


Java solution:
class Solution {
  public int minPushBox(char[][] grid) {
    record T(int boxX, int boxY, int playerX, int playerY) {}
    final int m = grid.length;
    final int n = grid[0].length;
    int[] box = {-1, -1};
    int[] player = {-1, -1};
    int[] target = {-1, -1};

    for (int i = 0; i < m; ++i)
      for (int j = 0; j < n; ++j)
        if (grid[i][j] == 'B')
          box = new int[] {i, j};
        else if (grid[i][j] == 'S')
          player = new int[] {i, j};
        else if (grid[i][j] == 'T')
          target = new int[] {i, j};

    Queue<T> q = new ArrayDeque<>(List.of(new T(box[0], box[1], player[0], player[1])));
    boolean[][][][] seen = new boolean[m][n][m][n];
    seen[box[0]][box[1]][player[0]][player[1]] = true;

    for (int step = 0; !q.isEmpty(); ++step)
      for (int sz = q.size(); sz > 0; --sz) {
        final int boxX = q.peek().boxX;
        final int boxY = q.peek().boxY;
        final int playerX = q.peek().playerX;
        final int playerY = q.poll().playerY;
        if (boxX == target[0] && boxY == target[1])
          return step;
        for (int k = 0; k < 4; ++k) {
          final int nextBoxX = boxX + DIRS[k][0];
          final int nextBoxY = boxY + DIRS[k][1];
          if (isInvalid(grid, nextBoxX, nextBoxY))
            continue;
          if (seen[nextBoxX][nextBoxY][boxX][boxY])
            continue;
          final int fromX = boxX + DIRS[(k + 2) % 4][0];
          final int fromY = boxY + DIRS[(k + 2) % 4][1];
          if (isInvalid(grid, fromX, fromY))
            continue;
          if (canGoTo(grid, playerX, playerY, fromX, fromY, boxX, boxY)) {
            seen[nextBoxX][nextBoxY][boxX][boxY] = true;
            q.offer(new T(nextBoxX, nextBoxY, boxX, boxY));
          }
        }
      }

    return -1;
  }

  private static final int[][] DIRS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

  // Returns true if (playerX, playerY) can go to (fromX, fromY).
  private boolean canGoTo(char[][] grid, int playerX, int playerY, int fromX, int fromY, int boxX,
                          int boxY) {
    Queue<Pair<Integer, Integer>> q = new ArrayDeque<>(List.of(new Pair<>(playerX, playerY)));
    boolean[][] seen = new boolean[grid.length][grid[0].length];
    seen[playerX][playerY] = true;

    while (!q.isEmpty()) {
      final int i = q.peek().getKey();
      final int j = q.poll().getValue();
      if (i == fromX && j == fromY)
        return true;
      for (int[] dir : DIRS) {
        final int x = i + dir[0];
        final int y = j + dir[1];
        if (isInvalid(grid, x, y))
          continue;
        if (seen[x][y])
          continue;
        if (x == boxX && y == boxY)
          continue;
        q.offer(new Pair<>(x, y));
        seen[x][y] = true;
      }
    }

    return false;
  }

  private boolean isInvalid(char[][] grid, int playerX, int playerY) {
    return playerX < 0 || playerX == grid.length || playerY < 0 || playerY == grid[0].length ||
        grid[playerX][playerY] == '#';
  }
}


C++ solution:
class Solution {
 public:
  int minPushBox(vector<vector<char>>& grid) {
    const int m = grid.size();
    const int n = grid[0].size();
    vector<int> box;
    vector<int> player;
    vector<int> target;

    for (int i = 0; i < m; ++i)
      for (int j = 0; j < n; ++j)
        if (grid[i][j] == 'B')
          box = {i, j};
        else if (grid[i][j] == 'S')
          player = {i, j};
        else if (grid[i][j] == 'T')
          target = {i, j};

    // (boxX, boxY, playerX, playerY)
    queue<tuple<int, int, int, int>> q{
        {{box[0], box[1], player[0], player[1]}}};
    vector<vector<vector<vector<bool>>>> seen(
        m, vector<vector<vector<bool>>>(
               n, vector<vector<bool>>(m, vector<bool>(n))));
    seen[box[0]][box[1]][player[0]][player[1]] = true;

    for (int step = 0; !q.empty(); ++step)
      for (int sz = q.size(); sz > 0; --sz) {
        const auto [boxX, boxY, playerX, playerY] = q.front();
        q.pop();
        if (boxX == target[0] && boxY == target[1])
          return step;
        for (int k = 0; k < 4; ++k) {
          const int nextBoxX = boxX + kDirs[k % 4][0];
          const int nextBoxY = boxY + kDirs[k % 4][1];
          if (isInvalid(grid, nextBoxX, nextBoxY))
            continue;
          if (seen[nextBoxX][nextBoxY][boxX][boxY])
            continue;
          const int fromX = boxX + kDirs[(k + 2) % 4][0];
          const int fromY = boxY + kDirs[(k + 2) % 4][1];
          if (isInvalid(grid, fromX, fromY))
            continue;
          if (canGoTo(grid, playerX, playerY, fromX, fromY, boxX, boxY)) {
            seen[nextBoxX][nextBoxY][boxX][boxY] = true;
            q.emplace(nextBoxX, nextBoxY, boxX, boxY);
          }
        }
      }

    return -1;
  }

 private:
  static constexpr int kDirs[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

  // Returns true if (playerX, playerY) can go to (fromX, fromY).
  bool canGoTo(const vector<vector<char>>& grid, int playerX, int playerY,
               int fromX, int fromY, int boxX, int boxY) {
    queue<pair<int, int>> q{{{playerX, playerY}}};
    vector<vector<bool>> seen(grid.size(), vector<bool>(grid[0].size()));
    seen[playerX][playerY] = true;

    while (!q.empty()) {
      const auto [i, j] = q.front();
      q.pop();
      if (i == fromX && j == fromY)
        return true;
      for (const auto& [dx, dy] : kDirs) {
        const int x = i + dx;
        const int y = j + dy;
        if (isInvalid(grid, x, y))
          continue;
        if (seen[x][y])
          continue;
        if (x == boxX && y == boxY)
          continue;
        q.emplace(x, y);
        seen[x][y] = true;
      }
    }

    return false;
  }

  bool isInvalid(const vector<vector<char>>& grid, int playerX, int playerY) {
    return playerX < 0 || playerX == grid.size() || playerY < 0 ||
           playerY == grid[0].size() || grid[playerX][playerY] == '#';
  }
};