leetcode
leetcode 2951 ~ 3000
打开转盘锁

打开转盘锁

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 91 ms, 内存: 16.7 MB


/*
 * 思路:
 * 1. 使用广度优先搜索(BFS)算法来解决这个问题。
 * 2. 从起始状态 "0000" 开始,通过每次旋转拨轮生成新的状态。
 * 3. 使用一个队列来存储当前状态和步数。
 * 4. 使用一个集合来存储死亡数字和已访问的状态,避免重复访问。
 * 5. 如果当前状态是目标状态,返回步数。
 * 6. 如果遍历完所有可能的状态仍未找到目标状态,返回 -1。
 * 7. 使用Java Stream来简化代码。
 */
import java.util.*;
import java.util.stream.*;

public class OpenLockStream {
    public int openLock(String[] deadends, String target) {
        Set<String> dead = Arrays.stream(deadends).collect(Collectors.toSet());
        Set<String> visited = new HashSet<>();
        Queue<String> queue = new LinkedList<>();
        queue.offer("0000");
        visited.add("0000");
        int steps = 0;

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String current = queue.poll();
                if (dead.contains(current)) {
                    continue;
                }
                if (current.equals(target)) {
                    return steps;
                }
                getNextStates(current).stream()
                    .filter(next -> !visited.contains(next))
                    .forEach(next -> {
                        queue.offer(next);
                        visited.add(next);
                    });
            }
            steps++;
        }
        return -1;
    }

    private List<String> getNextStates(String state) {
        return IntStream.range(0, 4).boxed()
            .flatMap(i -> Stream.of(
                new StringBuilder(state).replace(i, i + 1, String.valueOf((state.charAt(i) - '0' + 1) % 10)).toString(),
                new StringBuilder(state).replace(i, i + 1, String.valueOf((state.charAt(i) - '0' + 9) % 10)).toString()
            ))
            .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        OpenLockStream solution = new OpenLockStream();
        String[] deadends = {"0201", "0101", "0102", "1212", "2002"};
        String target = "0202";
        System.out.println(solution.openLock(deadends, target)); // 输出: 6
    }
}

解释

方法:

本题解采用了双向广度优先搜索(BFS)算法。首先将起始点'0000'和目标点target分别加入到两个集合q1和q2中。算法交替从q1和q2中的一个较小的集合扩展,以此达到双向搜索的目的。每一步都检查当前节点是否出现在对方集合中,如果出现,表示找到了最短路径。如果没有,就将当前节点的所有相邻节点(通过拨动锁一位得到)添加到临时集合中,然后将q1和q2交换,继续下一轮搜索。搜索过程中会跳过出现在deadends集合中的节点。

时间复杂度:

O(N)

空间复杂度:

O(N)

代码细节讲解

🦆
在双向BFS中,为何选择始终扩展较小的集合来提高效率?这样做的数学或算法基础是什么?
在双向BFS中,选择扩展较小的集合可以显著提高搜索效率,因为这样做可以减少每一步可能的搜索空间和计算量。具体来说,如果有两个集合A和B,A的大小小于B,那么从A出发的可能的新状态数量也相对较少。这种策略的数学基础在于减少了搜索的广度,因为在每一步中扩展的是节点数量较少的集合,理论上能减少重复计算和不必要的搜索,从而更快地找到交点,实现效率上的优化。这种方法在某种程度上类似于优化搜索树的扩展过程,减少了不必要的分支。
🦆
为了避免死锁,算法在遇到deadends中的节点时选择跳过。如果初始状态'0000'就在deadends中,算法是否有处理这种情况的机制?
是的,算法有处理这种情况的机制。在算法实现中,将deadends转换为集合deads,然后在每次遍历当前节点前都会检查该节点是否在deads中。如果起始节点'0000'在deads中,它会在第一次检查时被识别并跳过,此时由于没有其他节点可以扩展,搜索将结束并返回-1,表示无法到达目标。这样确保了算法在初始状态即为死锁时能正确地返回无解的结果。
🦆
在双向BFS的实现中,如何确保在两个队列都非空的条件下仍然能有效检测到路径的交点?
在双向BFS实现中,每次扩展节点时,都会检查当前节点是否已存在于对方的集合中。算法通过在每个扩展步骤中交替检查当前正在扩展的节点集合中的每个节点是否已经出现在对方集合中来确保有效检测到路径的交点。如果发现某个节点同时存在于两个集合中,这意味着从起点到该节点和从终点到该节点的路径在此节点处相遇,因此找到了从起点到终点的有效路径。此时算法将返回当前累计的步数作为最短路径的长度。这种交叉检查机制确保了即使在两个队列都非空的情况下,也能及时发现并处理路径的交点。

相关问题