当前位置: 首页 > news >正文

【C/C++】打开转盘锁(leetcode T752)

核心考点:广度优先搜索 (BFS)、哈希表、字符串

知识点补充:【语法】结构化绑定

题目描述

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

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

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

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

【测试用例见文末】

题目详解:

class Solution {
public:
    int openLock(vector<string>& deadends, string target) {
        // 如果目标是"0000",直接返回0,因为已经解锁
        if (target == "0000") {
            return 0;
        }

        // 将死亡数字集合转为哈希表,方便查询
        unordered_set<string> dead(deadends.begin(), deadends.end());
        
        // 如果初始状态 "0000" 就是死亡数字,返回 -1
        if (dead.count("0000")) {
            return -1;
        }

        // 定义辅助函数,获取当前数字的前一个数字
        auto num_prev = [](char x) -> char {
            return (x == '0' ? '9' : x - 1);  // '0' -> '9', 其他字符减去 1
        };
        
        // 定义辅助函数,获取当前数字的后一个数字
        auto num_succ = [](char x) -> char {
            return (x == '9' ? '0' : x + 1);  // '9' -> '0', 其他字符加 1
        };

        // 获取当前状态转动一个拨轮后的所有可能状态
        auto get = [&](string& status) -> vector<string> {
            vector<string> ret;
            for (int i = 0; i < 4; ++i) {  // 遍历每个拨轮
                char num = status[i];  // 获取当前拨轮的数字
                status[i] = num_prev(num);  // 尝试前一个数字
                ret.push_back(status);  // 添加到结果中
                status[i] = num_succ(num);  // 尝试后一个数字
                ret.push_back(status);  // 添加到结果中
                status[i] = num;  // 恢复原状态
            }
            return ret;
        };

        // 使用队列进行 BFS 搜索,队列中保存当前状态和步数
        queue<pair<string, int>> q;
        q.emplace("0000", 0);  // 从初始状态开始,步数为 0
        unordered_set<string> seen = {"0000"};  // 用哈希集合记录已经访问过的状态

        // BFS 循环
        while (!q.empty()) {
            auto [status, step] = q.front();  // 获取当前状态和步数
            q.pop();
            // 枚举当前状态可以转动到的所有新状态
            for (auto&& next_status: get(status)) {
                // 如果新状态未被访问过且不在死亡数字中
                if (!seen.count(next_status) && !dead.count(next_status)) {
                    // 如果新状态是目标状态,返回当前步数 + 1
                    if (next_status == target) {
                        return step + 1;
                    }
                    // 否则将新状态加入队列,步数 + 1,并标记为已访问
                    q.emplace(next_status, step + 1);
                    seen.insert(next_status);
                }
            }
        }

        // 如果队列为空,说明无法到达目标状态
        return -1;
    }
};

语法补充:

1.结构化绑定

Structured Binding)它用于从 pair(或 tuple)等类型中直接解包多个值。这个语法是在 C++17 引入的。 

auto [status, step] = q.front();

相当于,可以简化代码:

pair<string, int> p = q.front();  // 取出队首元素
string status = p.first;  // 获取状态
int step = p.second;  // 获取步数

2.辅助函数:Lambda函数

具体可看文章:【C++基础】Lambda 函数 基础知识讲解学习及难点解析-CSDN博客

思路分析:

  1. 题目分析:  这道题目本质上是一个状态转移问题,可以通过广度优先搜索(BFS)来解决。每个锁的状态可以视作一个图的节点,状态之间的转移(旋转拨轮)是图中的边,目标是从初始状态 "0000" 通过最少的操作到达目标状态 target。同时,要避免经过 deadends 中的状态,这些状态会将锁永久锁定。

  2. 基本思路

    • 如果目标状态是 "0000",直接返回 0,因为已经解锁。

    • 使用 BFS,从 "0000" 状态开始进行层级遍历,每次可以选择转动一个拨轮到相邻状态(前进或后退一位)。

    • 对于每个状态,检查它是否是 deadends 中的状态,如果是则跳过;如果不是,则加入队列继续搜索。

    • 如果到达目标状态 target,返回当前步数(即最小旋转次数)。

    • 如果队列为空,说明无法到达目标状态,返回 -1。

  3. 细节考虑

    • 使用 unordered_set 来存储 deadends 和已经访问过的状态,避免重复计算。

    • 定义了两个辅助函数 num_prevnum_succ 用于计算每次旋转的前一个或后一个数字。

    • 通过 get 函数生成当前状态的所有可达状态(即转动一个拨轮后可能的状态)。

  4. 时间和空间复杂度

    • 时间复杂度:BFS 搜索的时间复杂度是 O(N),其中 N 是状态空间的大小。由于每个拨轮有 10 个数字,四个拨轮的总状态数为 104=1000010^4 = 10000104=10000,所以时间复杂度是 O(10000)。

    • 空间复杂度:BFS 的空间复杂度是 O(N),需要存储队列和已访问的状态,最大空间复杂度为 O(10000)。

测试用例:

示例 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
解释:无法旋转到目标数字且不被锁定。

提示:

  • 1 <= deadends.length <= 500

  • deadends[i].length == 4

  • target.length == 4

  • target 不在 deadends 之中

  • target 和 deadends[i] 仅由若干位数字组成

相关文章:

  • Java EE期末总结(第四章)
  • VBA之Excel应用第四章第二节:单元格对象的行、列属性
  • Flask学习笔记 - 数据库
  • 网络安全应急响应-用户信息排查
  • 鸿蒙Arkts开发飞机大战小游戏,包含无敌模式,自动射弹,暂停和继续
  • 手写JSX实现虚拟DOM
  • Spring Boot 中的 Bean
  • 【C语言】数据在内存中的储存(整形)
  • C++使用Qt Charts创建数据可视化图表
  • MySQL 函数(入门版)
  • c++STL入门
  • 蓝桥杯嵌入式第十四届模拟二
  • Arduino示例代码讲解:Row-Column Scanning an 8x8 LED matrix with X-Y input LED矩阵
  • 提升工作效率的可视化笔记应用程序
  • HTML表单属性2
  • Nginx 生产配置文件
  • anomalib—2—输入图像大小调整
  • 28--当路由器开始“宫斗“:设备控制面安全配置全解
  • 鸿蒙开发04界面渲染
  • JavaScript promise实例——通过XHR获取省份列表
  • 网站推广公司大家好/成都seo网络优化公司
  • 泉州(晋江)网站建设/有什么功能
  • 武汉网站网站建设/抖音搜索seo排名优化
  • 厦门一个平台做网站啥的/十大软件免费下载网站排行榜
  • 新手怎么做网站内容维护/微信管理系统
  • 备案个人可以做视频网站/接外包项目的网站