The Maze III

update Sep 6, 2017 19:59

LeetCodearrow-up-right

There is a ball in a maze with empty spaces and walls. The ball can go through empty spaces by rolling up (u), down (d), left (l) or right (r), but it won't stop rolling until hitting a wall. When the ball stops, it could choose the next direction. There is also a hole in this maze. The ball will drop into the hole if it rolls on to the hole.

Given the ball position, the hole position and the maze, find out how the ball could drop into the hole by moving the shortest distance. The distance is defined by the number of empty spaces traveled by the ball from the start position (excluded) to the hole (included). Output the moving directions by using 'u', 'd', 'l' and 'r'. Since there could be several different shortest ways, you should output the lexicographically smallest way. If the ball cannot reach the hole, output "impossible".

The maze is represented by a binary 2D array. 1 means the wall and 0 means the empty space. You may assume that the borders of the maze are all walls. The ball and the hole coordinates are represented by row and column indexes.

Example 1

Input 1: a maze represented by a 2D array

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

Input 2: ball coordinate (rowBall, colBall) = (4, 3)
Input 3: hole coordinate (rowHole, colHole) = (0, 1)

Output: "lul"

Explanation: There are two shortest ways for the ball to drop into the hole. The first way is left -> up -> left, represented by "lul". The second way is up -> left, represented by 'ul'. Both ways have shortest distance 6, but the first way is lexicographically smaller because 'l' < 'u'. So the output is "lul".

Example 2

Explanation: The ball cannot reach the hole.

Note:

  • There is only one ball and one hole in the maze.

  • Both the ball and hole exist on an empty space, and they will not be at the same position initially.

  • The given maze does not contain border (like the red rectangle in the example pictures), but you could assume the border of the maze are all walls.

  • The maze contains at least 2 empty spaces, and the width and the height of the maze won't exceed 30.

Basic Idea:

这道题目困扰了很长时间。第一次见到这道题目,自己只会简单的dfs和bfs,尝试之后发现都不是很好的方法,于是打开Greg的课件开始复习,经过两天学习背景知识之后,开始尝试用 Dijkstra 算法来切入。

算法思想 概述:

  • 将节点的坐标、距离以及path封装进 Vertex class,做为保存在 distance[][] table 中和 pq 中的元素类型;

  • 在每次沿着四个方向分别搜索的时候,更新后需要加入 pq 和 distance table 的 Vertex 中要同时更新path (在后面 append 一个方向 char);

  • 为避免 side effect,distance 中和 pq 中 以及每次更新的 vertex 都必须是新建的;

  • 每次 poll 出之后检查当前 Vertex 是否就是 hole 位置,是的话直接返回其 path;

  • 一开始有一个疑问,Dijkstra 能否为我们找到所有最短路径?

    答案是可以,只要每次更新neighbor的distance时候,当 oldDistance == newDistance 的时候,相应更新pred,这种情况下每个节点的pred应该是一个list,这样最终就可以重建所有路径;

  • 接下来面临的问题是先找到所有最短路径,再按照字典顺序排序,实现起来会否过于复杂?

    其实我们不必生成所有最短路径,只需要在更新的过程中采用合适的排序策略。在JAVA中,我们可以override compareTo 函数,实现 1st key: distance, 2nd key: path 的比较规则,然后利用这种规则对neighbor 进行更新。

  • 第三个问题是如何解决 hole 不在墙边的问题(想到如果需要继续搜索,不可以从hole的位置开始)?

    当我们遇到 hole 的时候,可以直接 break 这条路,将hole及其相应 distance 和 path 加入 pq。跟据 Dijkstra 算法的定义,当 hole 的节点在 pq 顶端 poll 出时,它一定已经完成了最短路径的搜索,所以无需担心,可以直接返回。可能有人会担心可能会有同样路径长度,但是字典顺序更小的路径仍未发现,担心直接返回会有不妥。但事实上是不需要担心的,因为 pq 排序的机制已经由我们之前 override 的 compareTo 函数确定,考虑了path的问题。

Java Code:

Python Code:

重要一点: 前情回顾,在做到 Top K Frequent Wordsarrow-up-right 这道题目的时候,我说过在python中,如果要以 string 的字典顺序作为第二个 key 实现 priority queue 的比较规则很难,现在有了新的解决方法,就是新建一个inner class,然后 override __lt__ 方法,相当于 Java 中的 interface Comparable 中的 compareTo 方法。如此就可以实现以任意比较策略进行 heapq 操作的目的。

update 2018-05-25 21:53:35

C++ Solution

时隔几个月,又复习了一下写 Dijkstra 的细节,也犯了一些细节上的错误,耽误了很长时间。

  • 记录已经 generate 过的点的当前最短 dist 是必须的,因为在 generate 一个点后,我们需要比较其当前dist和之前dist来确定其是否需要重新入队(即lazy deletion 地更新pq中的该点的dist);

  • 每次从pq中拿出当前 pq 中最近点之后,要判断之前是否已经给该点确定了更短的 dist,因为该点可能是之前已经被更新过的 (lazy deletion);

  • 掉入 hole 的时候可以直接 break,就会在之后被直接加入 pq 中。

update 2018-07-27 00:08:03

Update Java

这次实现中,记录visited使用了hashmap而不是之前的2d array,执行速度有所下降。