leetcode
leetcode 651 ~ 700
打开转盘锁

打开转盘锁

难度:

标签:

题目描述

代码结果

运行时间: 864 ms, 内存: 15.8 MB


/*
 * Problem: You have a lock with four circular dials. Each dial has 10 digits: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'.
 * The dials can rotate freely: e.g., '9' can turn into '0', and '0' can turn into '9'.
 * Each move can rotate one dial by one digit.
 * The lock's initial state is '0000'.
 * A list called deadends contains numbers that cause the lock to be permanently locked if reached.
 * The string target represents the number to unlock the lock. You need to find the minimum number of moves to unlock the lock, or return -1 if it's not possible.
 */
 
import java.util.*;
import java.util.stream.*;
 
public class OpenLockStream {
    public int openLock(String[] deadends, String target) {
        Set<String> deadSet = Arrays.stream(deadends).collect(Collectors.toSet());
        if (deadSet.contains("0000")) return -1;
        if (target.equals("0000")) return 0;
 
        Set<String> visited = new HashSet<>();
        visited.add("0000");
 
        Queue<String> queue = new LinkedList<>();
        queue.offer("0000");
 
        int moves = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            moves++;
            for (int i = 0; i < size; i++) {
                String current = queue.poll();
                List<String> nextStates = IntStream.range(0, 4)
                    .mapToObj(j -> Stream.of(-1, 1)
                        .map(d -> rotate(current, j, d))
                        .collect(Collectors.toList()))
                    .flatMap(Collection::stream)
                    .collect(Collectors.toList());
                for (String next : nextStates) {
                    if (next.equals(target)) return moves;
                    if (!visited.contains(next) && !deadSet.contains(next)) {
                        queue.offer(next);
                        visited.add(next);
                    }
                }
            }
        }
        return -1;
    }
 
    private String rotate(String state, int index, int direction) {
        char[] chars = state.toCharArray();
        chars[index] = (char)((chars[index] - '0' + direction + 10) % 10 + '0');
        return new String(chars);
    }
}
 

解释

方法:

此题解采用了宽度优先搜索(BFS)算法来找到从初始状态'0000'到目标状态'target'的最短路径。每个状态表示为一个四位数字的字符串,每次操作可以将任意一位数字增加1或减少1。如果增加或减少后的数字是9或者0,则会循环到0或9。使用队列来存储每一层的所有状态,并用一个集合来记录已访问过的状态和死亡数字。从'0000'开始搜索,每次从队列中取出当前状态,并生成新的状态,如果新状态没有被访问过且不在死亡数字中,则加入队列继续搜索。如果找到目标状态,则返回当前的步数。如果队列为空还没有找到目标状态,返回-1。

时间复杂度:

O(N)

空间复杂度:

O(N)

代码细节讲解

🦆
为什么在宽度优先搜索中使用队列而不是栈?队列和栈在这种搜索中有什么不同的影响?
在宽度优先搜索(BFS)中使用队列而不是栈是因为队列遵循先进先出(FIFO)的原则,这有助于按层次(从起点开始的距离)探索所有可能的状态。这种方式确保了一旦找到目标状态,就是通过最短的路径达到的。相反,栈遵循后进先出(LIFO)的原则,它会导致深度优先搜索(DFS),这种搜索方式首先探索尽可能深的路径而不是最短路径,可能会错过最短路径解,在解决此类找最短路径的问题时不是最佳选择。
🦆
在检查到当前状态为目标状态时直接返回步数的逻辑是否考虑了所有可能的路径到达目标状态的最小步数?
是的,在BFS中,由于搜索是层次进行的,当首次到达某个状态时,可以保证是通过最短路径到达的。因此,一旦在搜索过程中遇到目标状态,立即返回的步数即是到达该状态的最短路径。此时无需考虑其他可能的路径,因为它们要么已经被探索过,要么路径更长。
🦆
函数up和down在处理边界条件(数字为0或9时)的策略是什么?这种处理方式是否最优?
函数`up`和`down`处理边界条件的策略是,当需要增加数字9时,它会回绕到0,而当需要减少数字0时,它会回绕到9。这种处理方式是对转盘锁的自然模拟,因为转盘锁是循环的。这种处理方式是最优的,因为它正确并且直接地反映了问题的实际物理约束。
🦆
代码中有一个检查'0000'是否在deadends中的步骤,如果'0000'在deadends列表中,为什么直接返回-1?
如果'0000'在deadends列表中,意味着初始状态是锁定状态,无法开始任何操作来改变锁的状态。在这种情况下,由于起始点已被阻塞,不存在任何可能的路径可以从'0000'达到目标状态。因此,直接返回-1是合理的,表示问题无解。

相关问题