题目中输入初始状态和目标状态,返回最少的移动步数。
这与迷宫问题的最短路劲有些许相似,但这里更多的是一种状态的思考,本题涉及到“康托展开”这个特殊的哈希函数。
由于BFS一般用队列来实现,其实看到这个题目,可以很容易看到几个状态。假设空白格子中数字为0,那么按照搜索的思路,肯定是四个方向搜索,那么0的位置可以向左上右下四个方向移动,移动过后的状态又是一个新的状态。接着从新的状态继续发散,这不就是BFS队列方法的的入队出队嘛,和上一讲中的BFS迷宫问题2的思路是一样的,这样一想,似乎并不难,且也就那样。
但是,思路有了不代表能做对,因为AC还要考虑时间复杂度,空间复杂度问题,不过一般空间复杂度都没太大的问题。那么为什么这里说不能做对?这里参考书上的图
图中画红色三角形的两个状态明显是重复的状态,也就是说这个状态已经被搜索过了。那么从搜索过的状态再进行延伸,就会有很多重复的搜索,浪费了大量的时间。那么这题的关键就是如何去重,去重的方式可以参考爬楼梯问题的三部曲(普通递归,记忆性递归,动态规划-递推),也就是标记走过的点,这里就是标记走过的状态。
那么这里题目是一个3*3的数组,类比迷宫问题,发现我们可以用坐标的方式去做。但此时又有问题,坐标标记状态?这显然不可行,因为随着0的移动,这个棋盘的状态是在改变的,而迷宫问题中,迷宫是固定的,点的标记只是为了回溯的时候方便(避免搜索重复)。而且就算能按照之前的方式来做,那么这个去重的过程,要涉及到的状态n!×n!是否又太过于复杂?
这里两个点要考虑:①状态怎么表示?②能不能用坐标做?③去重到底怎么去重?
答案:数组表示,能,用康托展开。
先说状态怎么表示:状态只需要用一维数组存放即可,数组的大小就是n×n,其实这就是将从第二行开始的每一行直接放在第一行后面进行存放。
再说坐标:按照一维数组的存放状态,那么每n个就可以确定一行,列的确定只需要除n。举个例子:假设在数组{0,1,2,3,4,5,6,7,8}中,我想知道5在3*3的格子中处于哪个位置(以左上角为(0,0)),那么6/3就是行x,6%3的就是列y,这里的6是指5在数组中第六个。这里的解释和书上代码有些不同,具体实现我还没试过。
最后是去重的康托展开:
康托展开的时间复杂度分析:
最后是代码:
最后提醒: