微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

打开锁 - LeetCode,为什么计数器在每次递归调用中不断增加? 示例 1深度优先与广度优先

如何解决打开锁 - LeetCode,为什么计数器在每次递归调用中不断增加? 示例 1深度优先与广度优先

我正在处理 LeetCode 上的 Open the lock 挑战:

您面前有一个带有 4 个圆形轮子的锁。每个轮子有 10 个槽位:'0','1','2','3','4','5','6','7','8','9'。轮子可以自由旋转和环绕:例如我们可以将 '9' 转为 '0',或将 '0' 转为 '9'。每次移动都包括一个轮子转一个槽。

锁最初从 '0000' 开始,一个代表 4 个轮子状态的字符串。

您会看到一个 deadends 死胡同的列表,这意味着如果锁显示这些代码中的任何一个,锁的轮子将停止转动,您将无法打开它。

给定一个 target 表示将解锁锁的轮子的值,返回打开锁所需的最小总圈数,如果不可能,则返回 -1。

示例 1

Input: deadends = ["0201","0101","0102","1212","2002"],target = "0202"
Output: 6

这是我的尝试:

var openLock = function(deadends,target) {
    let res = 0;
    let seen = []
    let recursion = function(temp,counter=0){
        if(deadends.includes(temp) || seen.includes(temp)) return
        seen.push(temp)
        if(temp ===target){
            res = counter
            return
        }
        for(let i=0; i<temp.length; i++){
            let s1 = temp.substring(0,i) + (+temp[i]+1)%10 + temp.substring(i + 1)
            let s2 = temp.substring(0,i) + (+temp[i]+9)%10 + temp.substring(i + 1)
            recursion(s1,counter+1)
            erecursion(s2,counter+1)
        }
    }
    recursion('0000')
    return res ?? -1;
};

这里示例的输出是 2230,我不明白为什么。就好像 counter 变量值在每次递归调用中都会更新。

解决方法

发生这种情况是因为您的代码将每个访问过的组合标记为“已看到”,而没有确保您通过最短路径到达该组合,因此您的代码永远不会尝试使用较短的路径。

对于示例输入,您的代码将按以下顺序访问组合:

0000
1000
2000
...
9000
0100
1100
2100
...
9100
0200
1200
...
...

...这一切都发生了没有回溯!由于您的 for 循环总是会找到一些尚未访问过的可能性,因此递归只会不断加深。

...最终它会命中目标组合,但要经过一个非常长且深的递归路径。然后将该组合标记为可见,因此您的代码永远不会考虑可能导致相同解决方案的较短路径。

深度优先与广度优先

虽然您可以使用深度优先搜索使其工作,但使用广度优先搜索更容易解决这个问题,因为它更适合查找最短路径。使用深度优先搜索,您应该只将 当前 路径上的组合标记为“已看到”。所以当从递归中回溯时,那些更深的节点应该是未标记的。

这是广度优先的解决方案:

var openLock = function(deadends,target) {
    if (target == "0000") return 0; // boundary case
    let seen = new Set(deadends);
    if (seen.has("0000")) return -1; // boundary case
    let frontier = ["0000"];
    let res = 1;
    while (frontier.length) {
        let nextFrontier = [];
        for (let combi of frontier) {
            for (let dial = 0; dial < 4; dial++) {
                for (let add = 1; add < 10; add += 8) {
                    let neighbor = combi.slice(0,dial) + 
                                   (+combi[dial] + add) % 10 +
                                   combi.slice(dial+1);
                    if (!seen.has(neighbor)) {
                        if (neighbor == target) return res;
                        nextFrontier.push(neighbor);
                        seen.add(neighbor);
                    }
                }
            }
        }
        frontier = nextFrontier;
        res++;
    }
    return -1;    
};

此解决方案可以进一步优化。例如,add 值现在总是 first 1,然后 then 9,但是当它“更接近”时,先尝试 9 可能是有益的到该拨号上的目标数字。这将避免在另一个方向搜索的所有工作,因为在这种情况下,9 路很可能会导致更短的解决方案路径。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。