Leetcode 753. Cracking the Safe 双端队列实现 给出证明思路

题意

  • 我们希望构造一个最短的字符串,这个字符串每位可以是0k-1的字符,并且这个字符串的所有n长子串可以包含所有的 nk n k 种情况(即包含所有用k个字符构建的n长串)

思路

  • 不难想象,如果构造的这个串每一个n长子串恰好就是一个unique的情况,那么一定就是最短的串,长度为 nk+n1 n k + n − 1
  • 先假设我们一定可以构造出这样的串。然后可以换一个角度来看这个问题,设每一种独特的子串是一个节点,如果一个节点的n-1长后缀恰好另一个节点n-1长的前缀,那么我们就给这两个节点连一条边,因为这两个子串是可以在我们构造的串中直接相连的。这样我就构造了一个有向图,不难证明这个图是一个强连通图,即任意两点之间都是有通路的。那么我们假设的前提,就对应了这个图一定存在一条汉密尔顿通路。而这个图本身不大,我们就完全可以用bfs或者dfs去解这个题了。
  • 但是这个假设是否成立却是需要证明的,我参考了Discussion中的方法(Discussion中的原文),这里我给出一个我个人觉得可能更好理解一些的证明。

证明

  • 我们想证明上述假设,只需要给出一种构造方法,然后证明这个方法保证可以得到一条哈密尔顿通路
  • 首先我们定义一些符号,方便后面说明,我们用小写字母表示一个字符,大写字母加尖括号表示一个n-1长的子串,例如a<A>就表示了后缀为A的一个n长子串
  • 第二我们给出构造方法:
    • (1)随意选一个n长子串(或称节点)加入deque Q
    • (2)循环,取Q中最后一个节点,记为a<A>,然后选择一个没有加入deque中的节点<A>b插入队尾
    • (3)如果所有形如<A>b的节点都在队列中,则弹出队首元素,并把它插入队尾
    • (4)循环至队列元素个数到 nk n k
    • 这样这个deque中就保存了一条哈密尔顿通路
  • 第三我们证明这个构造方法是正确的,那么需要证明三个事情:首先是序列合法性,即第i个节点和第i+1节点之间应是有边的;二是算法可终止性,即最终一定能得到长度为 nk n k 的队列;三是节点的独特性,即序列中任意两个节点是不同的。其中第三个是显然保证的,我们主要证明前两点。
    • 证明序列合法性,主要就是证明算法第(3)步中队首元素<B>b和队尾元素a<A>,满足A = B这个条件。出现(3)的情况,对应了 x[0,k),<A>xQ ∀ x ∈ [ 0 , k ) , < A > x ∈ Q ,也就是说有k个形如<A>x的节点已经在队列中了。假如这些节点都没有出现在队首,那么包含队尾元素,应该有k+1个形如x<A>的节点出现在队列中,显然这是不可能的,因此,队首元素一定也是形如<A>x的,那么把它换到队尾,显然可以保证合法性。
    • 证明队列Q长度可以到达 nk n k 。反证法,设当Q长度为m时,就找不到下一个没有出现过的节点了。根据我们算法的(2)(3)步骤,我们知道当队尾元素找不到下一个节点时,我们会尝试下一个队首元素,如果完全无法找到下一个节点,说明Q中所有节点都无法连到下一个没有出现过的节点了。这就说明这m个节点构成了一个联通分量,总体至少有两个联通分量,这和我们一开始知道的这个图是强联通的,应该只有一个联通分量矛盾。因此我们一定可以构造到 nk n k 长的Q
    • 得证。

实现

class Solution {
public:
    int pow(int x, int n){
        int ret = 1;
        for (int i = 0; i < n; i++){
            ret *= x;
        }
        return ret;
    }
    string crackSafe(int n, int k) {
        deque<string> q;
        unordered_set<string> mapp;
        q.push_back(string(""));
        for (int i = 0; i < n; i++){
            *q.begin() += '0';
        }
        mapp.insert(q.back());
        int len = pow(k, n) ;
        while (q.size() < len){
            string now = q.back().substr(1, n - 1);
            bool flag = true;
            for (char i = '0'; i < k + '0'; i++){
                now += i;
                if (mapp.find(now) == mapp.end()){
                    flag = false;
                    mapp.insert(now);
                    q.push_back(now);
                    break;
                }
                now.pop_back();
            }
            if (flag){
                now = q.front();
                q.pop_front();
                q.push_back(now);
            }
        }
        string ret;
        for (auto& it : q){
            ret += it[0];
        }
        ret.pop_back();
        ret += q.back();
        return ret;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值