33.打开转盘锁

一、题目描述

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

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

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

字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
在这里插入图片描述
在这里插入图片描述

二、解题思路

先来分析题目,转盘锁带有四个拨轮,每个拨轮每次可以向上或者向下拨,也就是说当前转盘锁的状态转变到下一个状态有8种可能的选择,以’0000’为例:

在这里插入图片描述
对于当前的某种状态需要遍历每一种它的下一个状态,如果将这抽象成一张图这就很类似于广度优先搜索了(广度优先所有在我的LeetCode刷题笔记的第31题—二叉树的层次遍历有详细介绍,这里就不赘述了),像是在该图上寻求最短距离。

另外注意一个条件:就是想要拨到target的代表的解锁数字,经过的过程必须跳过可能出现的deadends包含的死亡数字。

target为’0000’时返回-1。

三、代码演示

class Solution {
    public int openLock(String[] deadends, String target) {
        //使用一个set集合,存放死亡数组中的密码值
        Set<String> deads = new HashSet<>();
        for(String s:deadends){
            //遍历死亡数组中的值,存入deads集合
            deads.add(s);
        }

        //用来判断某一密码值相邻密码值是否重复出现,防止死循环
        Set<String> visited = new HashSet<>();
        //Queue队列,广度优先搜索会用到的,存储当前状态所对应下一个状态的8种情况
        Queue<String> queue = new LinkedList();
        queue.add("0000");
        visited.add("0000");

        //声明初始步数,记录共拨了几次转盘
        int step = 0;
        //当所有可能的密码值都转到时结束
        while(!queue.isEmpty()){
            int len = queue.size();

            //遍历队列中的密码值
            for(int i=0; i<len; i++){
                //poll方法为出队操作,将8中情况对应的值依次出队
                String s = queue.poll();
                //如果死亡数组中包含密码值,跳过循环,因为这时密码值已经被锁定,不能进行下一步。
                if(deads.contains(s)){
                    continue;
                }
                //遍历到某一步的值等于target,返回step
                if(s.equals(target)){
                    return step;
                }

                //遍历当前密码值的相邻8个密码值并作判断是否重复出现
                //这里的4指的是4个拨轮,每个拨轮都有向上和向下拨的两种状态
                for(int j=0; j<4; j++){
                    //调用up方法,判断每个拨轮向上拨可能出现的情况
                    String up = up(s, j);
                    String down = down(s, j);

                    if(!visited.contains(up)){
                        queue.add(up);
                        visited.add(up);
                    }
                    if(!visited.contains(down)){
                        queue.add(down);
                        visited.add(down);
                    }
                }
            }
            //当前图的第一层遍历
            step++;
        }
        //无解,返回-1
        return -1;
    }

    //该方法将轮盘的每个拨轮向上拨
    public String up(String s, int j){
        //声明一个字符串数组存放当前的拨轮位置的字符数字
        char[] c = s.toCharArray();
        if(c[j]=='9'){
            c[j] = '0';
        }else{
            //当前拨轮对应数字加一
            c[j]++;
        }
        return new String(c);
    }

    //该方法将轮盘的每个拨轮向下拨
   public String down(String s, int j){
        //声明一个字符串数组存放当前的拨轮位置的字符数字
        char[] c = s.toCharArray();
        if(c[j]=='0'){
            c[j] = '9';
        }else{
            //当前拨轮对应数字减一
            c[j]--;
        }
        return new String(c);
    }
}

代码部分详解:

第11行:为什么要声明一个set集合存放某一密码值相邻密码值是否重复出现,防止死循环呢?如果只存在一个队列来存放当前位置的8种情况,会出现“走回头路的情况”,假如初始位置是’0000’,那么当拨到’1000’时将它放到队列里面,但是当’1000’被拿出队列,这是需要其它7种情况对应的数字,这是又要拨回去从’0000’开始,它又会对应8情况,这样会陷入死循环。所以这时候需要新的集合,存储那些已经遍历的集合:

1.若当前新拨出来的密码没有出现过,则将该密码加入到队列和 visited 集合中;

2.若当前新拨出来的密码已经在之前的遍历过程中出现过,此时不需要进行任何操作,继续遍历被拨密码的下一个位置即可。

集合,存储那些已经遍历的集合:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值