LeetCode 753. Cracking the Safe

所有可能的密码总数 k^n,如果把这些密码都拼起来,长度 n*k^n

如果每个密码与前一个密码共用 n-1 位,那么长度缩减为 k^n + (n-1)

上述字符串也被称为 De Bruijn sequence。

所以问题转变为,如果得到这个序列。很容易想到转化为图的问题来做,而且可以转化不同的问题。

 

 -> Hamilton Path

如果把所有可能的密码分别作为节点,通过共用 n-1 位转换得到的节点间添加有向边 (因此每个节点有 k 条边),如 X000 -1-> 0001 。原问题就转化为了寻找 Hamilton Path 的问题。但是判断以及寻找 Hamilton Path 是 NP-Complete 问题,因此只能暴力回溯去做。最后的答案就是 初始选择的节点+转换时添加位。

由于本题的图的对称性以及别的较好的性质,Hamilton Path 是一定存在的,而且甚至不需要回溯就能找到路径。但是由于主题思路还是回溯,回溯部分代码还是添加上使得思路更加清晰。

class Solution {
public:
    string res;
    
    string crackSafe(int n, int k) {
        int size=pow(k,n); // number of all possible passwords 
        res.resize(n,'0');
        unordered_set<string> visited{res};
        if (dfs(string(n,'0'),size,k,visited))
            return res;
        return "";
    }
    
    bool dfs(string node, int size, int k, unordered_set<string> &visited){
        if (visited.size()==size) return true;
        
        // n-1 digits of last password
        string suffix=node.substr(1);
        for (char ch='0';ch<'0'+k;++ch){
            string newNode=suffix+ch;
            if (!visited.count(newNode)){
                visited.insert(newNode); 
                res.push_back(ch);
                if (dfs(newNode,size,k,visited)) 
                    return true;
                res.pop_back(); 
                visited.erase(newNode);
            }
        }
        return false;
    }
};

 

-> Euler Circuit

每个密码的最后 n-1 位作为节点,通过共用这 n-1 位转换得到的节点间添加有向边,每个节点同样有 k 条边,如 000 -1-> 001 。原问题就转化为寻找 Euler Path 的问题,因为每个节点加上出去边上的那一位就是一种密码,要得到所有密码,必须访问每条边一次。要最短的字符串,就是 Euler Circuit 的问题。Eular Circuit 是可以在多项式时间内有解的。

Hierholzer's Algorithm 可以 O(V+E) 内找到 Euler Circuit,详见以下链接。由于链接里是把节点一次print出来,所以更加繁琐一点。https://www.geeksforgeeks.org/hierholzers-algorithm-directed-graph/

对于本题,我们只需要任意一个点加上所有路径上的字符即可。所有路径可以通过后序遍历轻松得到 (和上述链接做法本质一样)。其实连reverse都不用,因为是无向图。

class Solution {
public:
    string res;
    
    string crackSafe(int n, int k) {
        unordered_set<string> visited;
        res = "";
        dfs(string(n-1,'0'),k,visited);
        reverse(res.begin(),res.end());
        return string(n-1,'0')+res;
    }
    
    void dfs(string node, int k, unordered_set<string> &visited){
        for (char ch='0';ch<'0'+k;++ch){
            string newNode=node+ch;
            if (!visited.count(newNode)){
                visited.insert(newNode); 
                dfs(newNode.substr(1),k,visited);
                res.push_back(ch);
            }
        }
    }
};

时间复杂度 O(k*k^n),因为每次找边的时候,是for循环枚举的。如果用链表或者别的方式存储,时间复杂度为 O(k^n)。纯粹是for循环更好些才这么做的。

 

 

 

Reference:

https://leetcode.com/problems/cracking-the-safe/discuss/110265/Having-trouble-understanding-it-Try-this.

 

转载于:https://www.cnblogs.com/hankunyan/p/11009810.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值