• Leetcode752题打开转盘锁问题(BFS求解)


    首先介绍一下题目,网址如右:https://leetcode-cn.com/problems/open-the-lock/

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

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

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

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

      为什么会想到利用BFS,即广度优先搜索解决?首先分析题目,密码锁每处于一个状态时,下一次都有八种可能的状态去转换(每一位都有两种变化),可以将每一个字符串状态看成是树的一个节点,下面都连接着八个后继节点。

      那么好,问题转化成树了,紧接着就会想到深度优先搜索(DFS)和广度优先搜索(BFS),为什么用BFS呢,因为题目中求得是“最小旋转次数”,一旦涉及到最小的问题都应该想到用BFS求解。因为对于树来说,BFS是一层一层的去遍历,一旦在某一层找到结果就会直接返回,而DFS是一次搜索完一个分支,最终搜索完所有分支才得到结果,所以只要最优解不在树的最后一层,那么BFS所用的时间就会比DFS更少(因为访问的节点数更少).

      好了,既然解法有了,应该使用BFS,具体应该怎么做呢?

      BFS存储的数据结构最好用队列实现,队列里每次存储的是一层的树节点,访问完一层就将其全部推出继续加入下一层。一般来说,这类BFS求解的题目都有一个模板,大致如下:

      

    int solution(){
            Queue<XX> qu=new LinkedList<>();
            qu.offer(初始XX);  //初始值输入
            visited[];//存储已经访问过的状态,按照情况选择布尔数组或者set集合存储
            int res=0;   //初始化结果
            while(!qu.isEmpty()){
                int sz=qu.size();
                for(int i=0;i<sz;i++){
                    XX xx=qu.poll();
                    访问xx的后继状态,如果visited包含它或者有其他限制,则continue;
                    否则将其加入qu;     
                    同时加入visited中;
                }
                res++;   //这个时候res自增
            }
            return res;
        }

      模板已经有了,稍微思考一下就能得到下面的解法:

    public int openLock(String[] deadends, String target) {
            Queue<String> qu=new LinkedList<>();
            qu.offer("0000");//初始状态入队
            Set<String> vis=new HashSet<>();//存储已经到达过的状态
            Set<String> dead=new HashSet<>();  //存储死亡状态
            for(String s:deadends) dead.add(s);
            int res=0;  //存储步数
            while(!qu.isEmpty()){
                int sz=qu.size();
                for(int i=0;i<sz;i++){
                    String ss=qu.poll();
                    if(dead.contains(ss))
                        continue;
                    if(ss.equals(target))
                        return res;
                    for(int j=0;j<4;j++){    //关于后继状态如何增加,不同的题目不一样,要学会转化条件
                        String s1=upper(ss,j);
                        String s2=down(ss,j);
                        if(!vis.contains(s1)){
                            vis.add(s1);
                            qu.offer(s1);
                        }
                        if(!vis.contains(s2)){
                            vis.add(s2);
                            qu.offer(s2);
                        }
                    }
                }
                res++;
            }
            return -1;
        }
    
        //转盘锁顺时针旋转
        String upper(String s,int idx){
            char[] c=s.toCharArray();     //注意如何完成String与char数组的互相转化,以后可能还会用到
            if(c[idx]=='9') c[idx]='0';
            else c[idx]++;
            return new String(c);
        }
    
        //转盘锁逆时针旋转
        String down(String s,int idx){
            char[] c=s.toCharArray();
            if(c[idx]=='0') c[idx]='9';
            else c[idx]--;
            return new String(c);
        }

      该解法确实通过了Leetcode所有案例,虽然或许时间复杂度不是最优,但是应该是最为普遍,最广为适用的解法了。

      以上就是我对于这一题的解法,如有不足请在评论区批评指正。

  • 相关阅读:
    JS判断鼠标从什么方向进入一个容器
    jQuery最核心的基础设施之一——数据缓存模块进化史
    判定模块加载时是否存在循环依赖
    mass Framework fx模块 v4
    一个简单的加载系统
    MVC历史演化
    Mozilla Firefox 17 正式发布
    javascript 堆栈与列队
    被迫才是进步的原动力(转)
    jquery1.83 之前所有与异步列队相关的模块的演变回顾
  • 原文地址:https://www.cnblogs.com/lzjdsg/p/14074368.html
Copyright © 2020-2023  润新知