加载中...
剑指 Offer II 109-开密码锁
发表于:2021-12-03 | 分类: 中等
字数统计: 1.2k | 阅读时长: 5分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/zlDJc7

中文题目

一个密码锁由 4 个环形拨轮组成,每个拨轮都有 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" 时这个锁就会被锁定。

示例 2:

输入: deadends = ["8888"], target = "0009"
输出:1
解释:
把最后一位反向旋转一次即可 "0000" -> "0009"。

示例 3:

输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
输出:-1
解释:
无法旋转到目标数字且不被锁定。

示例 4:

输入: deadends = ["0000"], target = "8888"
输出:-1

 

提示:

  • 1 <= deadends.length <= 500
  • deadends[i].length == 4
  • target.length == 4
  • target 不在 deadends 之中
  • targetdeadends[i] 仅由若干位数字组成

 

注意:本题与主站 752 题相同: https://leetcode-cn.com/problems/open-the-lock/

通过代码

高赞题解

简单题,解释都在代码对应的注释中。
找“邻居”的方法代码写得有些冗余,可自行优化。

class Solution {
    public int openLock(String[] deadends, String target) {
        //用双向广度优先遍历方法,哈希集合代替队列,因为哈希集合判断一个节点是否访问过只需要O(1)的时间,整个算法可大幅提升时间复杂度
        Set<String> dead = new HashSet<>(Arrays.asList(deadends));
        if(target.equals("0000")) return 0;
        if(dead.contains("0000") || dead.contains(target)) return -1;
        //起始节点集合
        Set<String> set1 = new HashSet<>();
        //目标节点集合
        Set<String> set2 = new HashSet<>();
        set1.add("0000");
        set2.add(target);
        Set<String> vis = new HashSet<>();//记录已经访问(到达)过的邻居
        vis.add("0000");
        int len = 1; 
        //双向遍历可以避免访问很多不满足条件的节点从而大幅提高搜索效率
        while(!set1.isEmpty() && !set2.isEmpty()){
            //我们要保证从节点较小的那个集合开始广度遍历,这里固定从set1开始
            if(set1.size() > set2.size()){
                Set<String> tmp = set1;
                set1 = set2;
                set2 = tmp;
            }
            //set3用来保存set1一趟遍历的“邻居”
            Set<String> set3 = new HashSet<>();
            for(String s : set1){
                List<String> neighbors = getNeighbors(s);
                for(String neighbor : neighbors){
                    //如果邻居属于死亡节点,直接跳过
                    if(dead.contains(neighbor)) continue;
                    //如果本次遍历的邻居就是目标节点,则找到一条从起始到目标节点的最短路径
                    if(set2.contains(neighbor)) return len;
                    //如果本次遍历的“邻居”未曾到达,把它加进邻居集合里并标记已访问
                    if(!vis.contains(neighbor)){
                        set3.add(neighbor);
                        vis.add(neighbor);
                    }
                }
            }
            //到这里说明通过一趟遍历没有找到一条路径,就把路径长度+1,同时把本趟邻居集合当作下一趟开始遍历的起始节点集合
            len++;
            set1 = set3;
        }
        return -1;
    }

    //求传入的字符串“邻居”集合
    public List<String> getNeighbors(String s){
        List<String> ls = new LinkedList<>();
        char[] ch = s.toCharArray();
        StringBuilder sb = new StringBuilder(s);
        for(int i = 0; i < ch.length; ++i){
            char old = ch[i];
            ch[i] = ch[i] == '0' ? '9' : (char)(ch[i]-1);
            sb.setCharAt(i, ch[i]);
            ls.add(sb.toString());
            sb.setCharAt(i, old);
            ch[i] = old;
            ch[i] = ch[i] == '9' ? '0' : (char)(ch[i]+1);
            sb.setCharAt(i, ch[i]);
            ls.add(sb.toString());
            sb.setCharAt(i, old);
            ch[i] = old;
        }
        return ls;
    }
}

统计信息

通过次数 提交次数 AC比率
1880 3136 59.9%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言
上一篇:
剑指 Offer II 047-二叉树剪枝
下一篇:
剑指 Offer II 110-所有路径
本文目录
本文目录