携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
每日刷题 2022.07.30
- leetcode原题链接:leetcode.cn/problems/op…
- 难度:中等
- 方法:广度优先搜索,模运算
题目
- 你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有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
解释:无法旋转到目标数字且不被锁定。
提示
- 1 <= deadends.length <= 500
- deadends[i].length == 4
- target.length == 4
- target 不在 deadends 之中
- target 和 deadends[i] 仅由若干位数字组成
解题思路
- 根据题意分析:从初始的
'0000'节点开始,可以拨动转换每一个位置上的数字,且每一位上面只有0~9的数值可以选择,并且转换的字符串不能是出现在deadends数组中(因为存在这个数组中的字符串会导致这个锁🔒锁住,不能再转动,也就不能到达目标节点)。 - 询问:从开始节点到目标节点的解锁过程中,最小的旋转次数是多少?(注:每次只能旋转一个数字)
- 竟然是需要求解最小的旋转次数,那么可以将其想象成一个图,对于每一个节点的来说,其都有四位,每一位可以往前或者往后变化,总的来说对于每一个字符串一共有
2 * 4 = 8种情况,因此只需要对每一个节点的8种情况,进行筛选,将符合要求的放到接下来的步骤中再次循环操作,最先找到目标节点的层数,就是最小的旋转次数。 - 因此使用广度优先搜索
bfs,将开始节点放入到队列中,每次寻找下一层的节点放入,依次循环,最先找到目标节点的,就是最短最少的次数。
bfs需要注意的点
- 循环的层数
dep,如果题目中存在限定的层数127.单词接龙。那么就需要将while循环的判断条件进行改变
while(queue.length != 0 && k != 0) {
if(k == 0) return;
}
// 最后队列为空,都没有找到合适的,那么说明是永远也找不到,就返回-1
return -1;
- 对于最开始直接放入队列中的开始节点,一定要进行标记,防止被重复访问,造成死循环
- 在存储题目中所给的数组的时候,可以考虑下,如果每次都需要判断其是否存在于这个数组中,这样每次都需要遍历数组的长度,就会浪费时间。可以通过将数组转换成
map或者set这样的数据结构,在查询的操作上就会将时间优化到o(1) vis数组的必要性,有些时候因为路径是有长度的或者是有数值的,那么就不需要额外的vis数组再来记录了,可以通过路径的大小来判断其是否被遍历过。- 条件,如果在内部的
if判断过每个节点是否符合的情况,那么就不要再在开头进行判断了,因为其在上一轮的内部就已经被拦截住了,因此也就不会通过函数最开始的判断条件。因此操作可以直接在上一轮的内部书写完成。
新的方法的学习
-
charAt(idx)返回字符串中指定的下标的字符 -
str.charCodeAt(idx)- 返回值:指定
index处字符的UTF-16代码单元值的一个数字 - 如果
idx超出字符串的范围,则返回NaN - 如果没有指定
idx,则默认为0;如果有,需要填写大于等于0的数值
- 返回值:指定
-
取余操作:一圈整个是
10个数const up = (num + 1) % 10; const down = (num + 9) % 10; // 这样写就不用担心num - 1的时候,会超出,变成-1 -
回顾:判断一个数的奇偶性
if(num & 1) // num奇数 else // num偶数
AC代码
/**
* @param {string[]} deadends
* @param {string} target
* @return {number}
*/
var openLock = function(deadends, target) {
//就是查找,最小的路径,无须返回路径,需要将次数返回即可
// 广度优先搜索bfs
// 也就是0可以转到9、1,9可以转到0、8
// 需要特判,是否当前的节点就被锁死
if(deadends.indexOf('0000') != -1) return -1;
let queue = ['0000'], dep = 0, vis = new Map();
let set = new Set();
for(const one of deadends) {
set.add(one);
}
vis.set('0000', 1);
// 有可能会写一些,k--
while(queue.length != 0) {
let len = queue.length, q = [];
for(let i = 0; i < len; i++) {
let cur = queue[i];
if(cur == target) return dep;
// 对于每一个当前的元素,需要遍历其每一位进行波动
for(let j = 0; j < 4; j++) {
// let a = Number(cur[j]) + 1, b = Number(cur[j]) - 1;
// if(a == 10) {
// a = 0;
// }
// if(b == -1) {
// b = 9;
// }
// 避免字符串和数字一起操作,产生错误
const num = cur[j] - '0';
const up = (num + 1) % 10;
const down = (num + 9) % 10;
// 将其塞入到队列中
let stra = cur.substring(0, j) + up + cur.substring(j + 1);
let strb = cur.substring(0, j) + down + cur.substring(j + 1);
if(!set.has(stra) && !vis.has(stra)) {
vis.set(stra, 1);
q.push(stra);
}
if(!set.has(strb) && !vis.has(strb)) {
vis.set(strb, 1);
q.push(strb);
}
}
}
queue = q;
dep++;
}
return -1;
};