记一次队列和广度优先搜索问题(Leetcode.752 打开转盘锁)

Posted 王子靖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次队列和广度优先搜索问题(Leetcode.752 打开转盘锁)相关的知识,希望对你有一定的参考价值。

    打开转盘锁

    你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为  '0''0'变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。

    锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。

列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。

        第一次看到这个题目,一下让人没有方向,这是一个很经典的广度优先搜索问题,之前在Leetcode上是困难级别的问题,但后来将其修改为中级,接下来记录一下关于这道题的相关东西。


队列和广度优先搜索

    说到队列,是最基础的一种数据结构,就如表达的一样,队列就是数据进行排队的一种数据结构,先到达队列中的数据,会先出队列。队列只允许数据在队列表的前端进行删除,在队列表的末尾进行添加。在Java中java.util包下为我们提供了提供了队列的一种实现,当然,也可以自己进行实现。

     Queue<String> queue = new LinkedList<String>();

    queue.offer("队列的添加");

    queue.poll(); //返回队首元素,并在队列中删除

    在java.util的队列中,添加/移除元素还有add()和remove()方法,但会在处理异常的时候有异常抛出,在本例中,就不采用。




      关于广度优先搜索,简称BFS,一般用于遍历,或者是找出从根结点到目标结点的最短路径,简单来说就是:将根节点入队,然后遍历根节点的每子节点,并将每个遍历的子节点入队,将根节点出队。然后遍历整个队列,新添加的节点不会立即遍历,而是在下一轮中处理,直到队列为空,便遍历完整个需要遍历的节点。与树的层序遍历类似,越是接近根结点的结点将越早地遍历。因此可以用于遍历或是找出最短路径的问题。

    


关于该题


输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"

输出:6

解释:可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。

注意"0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,当拨动到 "0102" 时这个锁就会被锁定。

       Leetcode为我们列出了三个示例,在此列出一个以作说明。这道题就是一个求最短路径的问题,处理这个问题,首先要构建自己的一个图,也就是分析出每一个节点的子节点的存在情况。在这道题中,我们可以发现,每一个节点是有8个子节点的,因为锁的每一个节点是可以向上向下移动的,所以就会存在8情况:

    因此这就将该题转换为了一个BFS问题,根节点为 0000 找到到达目标数的一个最短路径,经过deadends的跳过该节点,直接遍历其他的。


Java 的一种实现和说明
private static int openLock(String[] deadends, String target) { //定义开始的位置 final String begin = "0000"; //为了查找快捷,将deadends,转换为为HashSet<String> HashSet<String> ban = new HashSet<>(Arrays.asList(deadends)); //当deadends中出现0000,直接返回无解 if(ban.contains(begin)) return -1; // 如果目标数位0000,直接返回0,不需要进行搜索 if(target.equals(begin)) return 0; //准备搜索,创建队列 Queue<String> queue = new LinkedList<>(); //初始化最短路径 int min = 0; //定义以已经查找过的数组,减去枝桠 HashSet<String> visited = new HashSet<>(); //搜索开始,将根节点入队 queue.offer(begin); //将根节点加入到已经遍历过的HashSet中 visited.add(begin); //开始循环,当队列为空,搜索完成 while (!queue.isEmpty()){ //距离自加,每一代的添加,就是距离的增加 ++min; //获取当前队列的长度,遍历完当前一代的节点 int index = queue.size(); for (int i = 0; i < index; i++) { //将当前队首的节点出队 String value = queue.poll(); //查他的每个子节点 for (int j = 0; j < 4; j++) { //每个子节点存在+-1的操作,用该循环是实现 for(int k = -1 ; k<=1 ;k+=2){ //为了提取出每一个位置的数字,将String 转换为char数组 char[] temp = value.toCharArray(); //拨动,将该位置的char转换位数字,在+-1加10取模,保证其为正数,再将其转换会char temp[j] = (char) ((temp[j]-'0'+k+10)%10+'0'); //将数组转换为String,便于比较 String next = String.valueOf(temp); //如果遍历成功,即可返回当前位置,搜索结束 if(next.equals(target)) return min; // 当遍历的数字出现再禁止的数字中,或者已经遍历过,跳过这次遍历 if(visited.contains(next) || ban.contains(next)) continue; // 将新产生的下代节点入队,下一次循环处理 queue.offer(next); //将当前的节点添加到已经遍历的Hashset中 visited.add(next); } } } } //搜索结束,没有结果,返回-1 return -1; }

仅供参考,欢迎指正!



以上是关于记一次队列和广度优先搜索问题(Leetcode.752 打开转盘锁)的主要内容,如果未能解决你的问题,请参考以下文章

深度优先搜索和广度优先搜索

广度优先搜索算法

图的广度优先遍历补分

广度优先搜索

Python数据结构-队列与广度优先搜索(Queue)

广度优先搜索遍历图