zoukankan      html  css  js  c++  java
  • LeetCode Notes_#752 打开转盘锁

    LeetCode Notes_#752 打开转盘锁

    Contents

    题目

    你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为  '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。
    锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。
    列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
    字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
    示例 1:

    输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
    输出:6
    解释:
    可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
    注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
    因为当拨动到 "0102" 时这个锁就会被锁定。

    提示:

    1. 死亡列表 deadends 的长度范围为 [1, 500]。
    2. 目标数字 target 不会在 deadends 之中。
    3. 每个 deadends 和 target 中的字符串的数字会在 10,000 个可能的情况 '0000' 到 '9999' 中产生。

    思路分析

    题目描述的是一个很具体的问题,但是乍一看是没有什么思路的,需要将问题转化,将问题抽象成一个状态转移的问题。
    0000~9999看作不同的状态,这些状态之间可以互相转换。
    转换的条件是,两个数字仅有一位相差1(0和9也算在内)。
    按照上述描述,可以画出一个状态转移图,图中有0000~9999这些节点,满足状态转移条件的节点之间有边连接。最小旋转次数就等于从起始状态0000target的最短路径。
    求最短路径的一个方法就是广度优先遍历BFS,广度优先遍历也就是层序遍历,从0000target的过程中,广度优先遍历的层数就是最短路径。具体写法类似于树的BFS。

    需要注意的点:

    1. 状态转移的过程不可以出现deadends里的元素,遇到了就直接跳过
    2. BFS过程中,可能出现重复访问的节点,所以还需要判断重复,如果是重复的节点就不入队
    3. 需要使用null作为层与层之间的分隔符,否则不好计数

    解答

    class Solution {
        public int openLock(String[] deadends, String target) {
            //两个HashSet,用于判断重复
            HashSet<String> dead = new HashSet<>();
            for(String str:deadends){
                dead.add(str);
            }
            HashSet<String> seen = new HashSet<>();
            //创建BFS用到的队列,先把遍历的起点‘0000’入队
            Queue<String> queue = new LinkedList<>();
            queue.offer("0000");
            //第一层只有只有一个节点,所以加入分隔符
            queue.offer(null);
            //计数器,记录路径的长度
            int depth = 0;
    
            while(!queue.isEmpty()){
                String cur = queue.poll();
                //null是层与层之间的分隔符
                if(cur == null){
                    //遇到null说明接下来的元素就是新的一层了,depth增加1
                    depth++;
                    //判断下一层是否还有元素,如果有,就要添加null到队尾(因为这时下一层已经全部入队)
                    //这时添加到队尾,刚好就可以分隔开下一层与下下层
                    if(queue.peek() != null)
                        queue.offer(null);
                }else if(cur.equals(target)){
                    return depth;
                //如果cur在dead当中,就不能从这个节点走,在循环中不做任何操作,直接进入下一轮循环
                //否则,就把cur的所有邻居节点入队,等待遍历
                }else if(!dead.contains(cur)){
                    //外层循环:4位数
                    for(int i = 0;i <= 3;i++){
                        //内层循环:加1或者减1(加-1)
                        for(int d = -1;d <= 1;d += 2){
                            //修改的那一位数字,记为newDigit
                            //求余是为了符合0,9之间的转换
                            int newDigit = ((cur.charAt(i) - '0') + d + 10) % 10;
                            //将newDigit拼接到cur字符串的合适位置
                            String StringNeibor = cur.substring(0,i) + newDigit + cur.substring(i+1);
                            //对于每个邻居节点,需要判断是否是已经访问过的,如果已经访问过,就不再入队
                            if(!seen.contains(StringNeibor)){
                                seen.add(StringNeibor);
                                queue.offer(StringNeibor);
                            }
                        }
                    }
                }
            }
            //最后没找到,就返回-1
            return -1;
        }
    }

    复杂度分析

    时间复杂度:O(N2 * AN + D)。我们用 A 表示数字的个数,N 表示状态的位数,D表示数组 deadends 的大小。在最坏情况下,我们需要搜索完所有状态,状态的总数为 O(AN)。对于每个状态,我们要枚举修改的位置,需要 O(N) 的时间,枚举后得到新的状态同样需要 O(N) 的时间。

    空间复杂度:O(AN + D)),用来存储队列以及 deadends 的集合。

  • 相关阅读:
    Java学习:线程实现方式
    Java学习:异常的概念
    HDFS 其他命令---fsck
    Linux 常用命令
    Linux命令注释—HDFS运维
    大数据简介
    FusionInsight大数据开发---Oozie应用开发
    FusionInsight大数据开发---Flume应用开发
    FusionInsight大数据开发---Streaming应用开发
    FusionInsight大数据开发---Redis应用开发
  • 原文地址:https://www.cnblogs.com/Howfars/p/13610460.html
Copyright © 2011-2022 走看看