← Static tasks

499. The Maze III

leetcode hard

#array#csharp#graph#hard#heap#leetcode#queue#search#string

Task

В лабиринте есть мячик с пустыми пространствами (0) и стенами (1). Мячик может катиться вверх, вниз, влево или вправо, пока не столкнется со стеной, затем выбрать новое направление. В лабиринте также есть отверстие, куда мячик упадет, если закатится в него.

Дан лабиринт размером m x n, позиция мяча ball и отверстия hole, где ball = [ballrow, ballcol] и hole = [holerow, holecol]. Верните строку instructions с кратчайшим путем мячика к отверстию. Если существует несколько вариантов, верните лексикографически минимальный. Если путь невозможен, верните "impossible". Ответ должен содержать 'u' (вверх), 'd' (вниз), 'l' (влево) и 'r' (вправо).

Расстояние — это количество пройденных пустых пространств от начальной позиции (исключительно) до конечной (включительно).

Вы можете предположить, что границы лабиринта — это стены. В примере ниже они не указаны.

Пример:

Input: maze = [[0,0,0,0,0],[1,1,0,0,1],[0,0,0,0,0],[0,1,0,0,1],[0,1,0,0,0]], ball = [4,3], hole = [0,1]

Output: "lul"

C# solution

matched/original
using System;
using System.Collections.Generic;
public class State : IComparable<State> {
    public int Row, Col, Dist;
    public string Path;
    public State(int r, int c, int d, string p) { Row = r; Col = c; Dist = d; Path = p; }
    public int CompareTo(State other) {
        return Dist == other.Dist ? string.Compare(Path, other.Path, StringComparison.Ordinal) : Dist - other.Dist;
    }
}
public class Solution {
    private static readonly int[][] Directions = { new[] {0, -1}, new[] {-1, 0}, new[] {0, 1}, new[] {1, 0} };
    private static readonly string[] TextDirections = { "l", "u", "r", "d" };
    public string FindShortestWay(int[][] maze, int[] ball, int[] hole) {
        int m = maze.Length, n = maze[0].Length;
        var heap = new PriorityQueue<State>();
        var seen = new bool[m, n];
        heap.Enqueue(new State(ball[0], ball[1], 0, ""));
        
        while (heap.Count > 0) {
            var curr = heap.Dequeue();
            if (seen[curr.Row, curr.Col]) continue;
            if (curr.Row == hole[0] && curr.Col == hole[1]) return curr.Path;
            seen[curr.Row, curr.Col] = true;
            
            foreach (var (dy, dx, direction) in Directions.Zip(TextDirections)) {
                int r = curr.Row, c = curr.Col, dist = 0;
                while (Valid(r + dy, c + dx, maze, m, n)) {
                    r += dy; c += dx; dist++;
                    if (r == hole[0] && c == hole[1]) break;
                }
                heap.Enqueue(new State(r, c, curr.Dist + dist, curr.Path + direction));
            }
        }
        return "impossible";
    }
    private bool Valid(int row, int col, int[][] maze, int m, int n) {
        return row >= 0 && row < m && col >= 0 && col < n && maze[row][col] == 0;
    }
}

C++ solution

auto-draft, review before submit
#include <bits/stdc++.h>
using namespace std;

// Auto-generated C++ draft from the C# solution. Review containers, LINQ and helper types before submit.
public class State : IComparable<State> {
    public int Row, Col, Dist;
    public string Path;
    public State(int r, int c, int d, string p) { Row = r; Col = c; Dist = d; Path = p; }
    public int CompareTo(State other) {
        return Dist == other.Dist ? string.Compare(Path, other.Path, StringComparison.Ordinal) : Dist - other.Dist;
    }
}
class Solution {
public:
    private static readonly int[][] Directions = { new[] {0, -1}, new[] {-1, 0}, new[] {0, 1}, new[] {1, 0} };
    private static readonly vector<string> TextDirections = { "l", "u", "r", "d" };
    public string FindShortestWay(int[][] maze, vector<int>& ball, vector<int>& hole) {
        int m = maze.size(), n = maze[0].size();
        var heap = new PriorityQueue<State>();
        var seen = new bool[m, n];
        heap.Enqueue(new State(ball[0], ball[1], 0, ""));
        
        while (heap.size() > 0) {
            var curr = heap.Dequeue();
            if (seen[curr.Row, curr.Col]) continue;
            if (curr.Row == hole[0] && curr.Col == hole[1]) return curr.Path;
            seen[curr.Row, curr.Col] = true;
            
            foreach (var (dy, dx, direction) in Directions.Zip(TextDirections)) {
                int r = curr.Row, c = curr.Col, dist = 0;
                while (Valid(r + dy, c + dx, maze, m, n)) {
                    r += dy; c += dx; dist++;
                    if (r == hole[0] && c == hole[1]) break;
                }
                heap.Enqueue(new State(r, c, curr.Dist + dist, curr.Path + direction));
            }
        }
        return "impossible";
    }
    private bool Valid(int row, int col, int[][] maze, int m, int n) {
        return row >= 0 && row < m && col >= 0 && col < n && maze[row][col] == 0;
    }
}

Java solution

matched/original
class State {
    int row, col, dist;
    String path;
    State(int r, int c, int d, String p) { row = r; col = c; dist = d; path = p; }
}

class Solution {
    int[][] directions = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
    String[] textDirections = {"l", "u", "r", "d"};
    int m, n;

    public String findShortestWay(int[][] maze, int[] ball, int[] hole) {
        m = maze.length; n = maze[0].length;
        PriorityQueue<State> heap = new PriorityQueue<>((a, b) -> a.dist == b.dist ? a.path.compareTo(b.path) : a.dist - b.dist);
        boolean[][] seen = new boolean[m][n];
        heap.add(new State(ball[0], ball[1], 0, ""));
        
        while (!heap.isEmpty()) {
            State curr = heap.remove();
            if (seen[curr.row][curr.col]) continue;
            if (curr.row == hole[0] && curr.col == hole[1]) return curr.path;
            seen[curr.row][curr.col] = true;
            
            for (State nextState : getNeighbors(curr.row, curr.col, maze, hole)) {
                heap.add(new State(nextState.row, nextState.col, curr.dist + nextState.dist, curr.path + nextState.path));
            }
        }
        return "impossible";
    }
    
    private List<State> getNeighbors(int row, int col, int[][] maze, int[] hole) {
        List<State> neighbors = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            int dy = directions[i][0], dx = directions[i][1], dist = 0, currRow = row, currCol = col;
            String direction = textDirections[i];
            while (valid(currRow + dy, currCol + dx, maze)) {
                currRow += dy; currCol += dx; dist++;
                if (currRow == hole[0] && currCol == hole[1]) break;
            }
            neighbors.add(new State(currRow, currCol, dist, direction));
        }
        return neighbors;
    }
    
    private boolean valid(int row, int col, int[][] maze) {
        return row >= 0 && row < m && col >= 0 && col < n && maze[row][col] == 0;
    }
}

Python solution

matched/original
import heapq

class State:
    def __init__(self, r, c, d, p):
        self.row = r
        self.col = c
        self.dist = d
        self.path = p

    def __lt__(self, other):
        return self.dist == other.dist and self.path < other.path or self.dist < other.dist

class Solution:
    def findShortestWay(self, maze, ball, hole):
        m, n = len(maze), len(maze[0])
        directions = [(0, -1), (-1, 0), (0, 1), (1, 0)]
        textDirections = ["l", "u", "r", "d"]
        
        heap = []
        heapq.heappush(heap, State(ball[0], ball[1], 0, ""))
        seen = [[False] * n for _ in range(m)]
        
        while heap:
            curr = heapq.heappop(heap)
            if seen[curr.row][curr.col]:
                continue
            if [curr.row, curr.col] == hole:
                return curr.path
            seen[curr.row][curr.col] = True
            
            for nextState in self.getNeighbors(curr.row, curr.col, maze, hole, directions, textDirections):
                heapq.heappush(heap, State(nextState.row, nextState.col, curr.dist + nextState.dist, curr.path + nextState.path))
        
        return "impossible"
    
    def getNeighbors(self, row, col, maze, hole, directions, textDirections):
        m, n = len(maze), len(maze[0])
        neighbors = []
        for i in range(4):
            dy, dx = directions[i]
            direction = textDirections[i]
            currRow, currCol, dist = row, col, 0
            while 0 <= currRow + dy < m and 0 <= currCol + dx < n and maze[currRow + dy][currCol + dx] == 0:
                currRow += dy
                currCol += dx
                dist += 1
                if [currRow, currCol] == hole:
                    break
            neighbors.append(State(currRow, currCol, dist, direction))
        return neighbors

Go solution

matched/original
package main

import (
  "container/heap"
)

type State struct {
  row, col, dist int
  path           string
}

type PriorityQueue []*State

func (pq PriorityQueue) Len() int           { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool { return pq[i].dist < pq[j].dist || (pq[i].dist == pq[j].dist && pq[i].path < pq[j].path) }
func (pq PriorityQueue) Swap(i, j int)      { pq[i], pq[j] = pq[j], pq[i] }
func (pq *PriorityQueue) Push(x interface{}) { *pq = append(*pq, x.(*State)) }
func (pq *PriorityQueue) Pop() interface{} {
  old := *pq
  n := len(old)
  item := old[n-1]
  *pq = old[:n-1]
  return item
}

var directions = [4][2]int{{0, -1}, {-1, 0}, {0, 1}, {1, 0}}
var textDirections = []string{"l", "u", "r", "d"}

func findShortestWay(maze [][]int, ball, hole []int) string {
  m, n := len(maze), len(maze[0])
  pq := &PriorityQueue{}
  heap.Init(pq)
  heap.Push(pq, &State{ball[0], ball[1], 0, ""})
  seen := make([][]bool, m)
  for i := range seen {
    seen[i] = make([]bool, n)
  }

  for pq.Len() > 0 {
    curr := heap.Pop(pq).(*State)
    if seen[curr.row][curr.col] {
      continue
    }
    if curr.row == hole[0] && curr.col == hole[1] {
      return curr.path
    }
    seen[curr.row][curr.col] = true

    for i := 0; i < 4; i++ {
      dy, dx := directions[i][0], directions[i][1]
      currRow, currCol, dist := curr.row, curr.col, 0
      for currRow+dy >= 0 && currRow+dy < m && currCol+dx >= 0 && currCol+dx < n && maze[currRow+dy][currCol+dx] == 0 {
        currRow += dy
        currCol += dx
        dist++
        if currRow == hole[0] && currCol == hole[1] {
          break
        }
      }
      heap.Push(pq, &State{currRow, currCol, curr.dist + dist, curr.path + textDirections[i]})
    }
  }
  return "impossible"
}

Explanation

Algorithm

Инициализация и вспомогательные функции

Создайте функцию valid для проверки, находится ли координата (row, col) в пределах лабиринта и является ли она пустым пространством. Создайте функцию getNeighbors для получения списка соседей для данной позиции. Двигайтесь в каждом направлении (вверх, вниз, влево, вправо) до встречи со стеной.

Запуск алгоритма Дейкстры

Инициализируйте очередь с начальной позицией мяча, где элементы с меньшим расстоянием имеют высокий приоритет, а при равных расстояниях выбирайте минимальную строку пути. Создайте структуру seen для отслеживания посещенных узлов.

Поиск кратчайшего пути

Пока очередь не пуста, извлекайте узел с наименьшим расстоянием. Если узел посещен, пропустите его. Если это отверстие, верните текущий путь. Отметьте узел как посещенный, добавьте его соседей в очередь, обновив расстояние и путь. Если пути нет, верните "impossible".

😎