1. 程式人生 > >Leetcode 753. Cracking the Safe 雙端佇列實現 給出證明思路

Leetcode 753. Cracking the Safe 雙端佇列實現 給出證明思路

題意

  • 我們希望構造一個最短的字串,這個字串每位可以是0k-1的字元,並且這個字串的所有n長子串可以包含所有的 n k 種情況(即包含所有用k個字元構建的n長串)

思路

  • 不難想象,如果構造的這個串每一個n長子串恰好就是一個unique的情況,那麼一定就是最短的串,長度為
    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)迴圈至佇列元素個數到 n k
    • 這樣這個deque中就儲存了一條哈密爾頓通路
  • 第三我們證明這個構造方法是正確的,那麼需要證明三個事情:首先是序列合法性,即第i個節點和第i+1節點之間應是有邊的;二是演算法可終止性,即最終一定能得到長度為 n k 的佇列;三是節點的獨特性,即序列中任意兩個節點是不同的。其中第三個是顯然保證的,我們主要證明前兩點。
    • 證明序列合法性,主要就是證明演算法第(3)步中隊首元素<B>b和隊尾元素a<A>,滿足A = B這個條件。出現(3)的情況,對應了 x [ 0 , k ) , < A > x Q ,也就是說有k個形如<A>x的節點已經在佇列中了。假如這些節點都沒有出現在隊首,那麼包含隊尾元素,應該有k+1個形如x<A>的節點出現在佇列中,顯然這是不可能的,因此,隊首元素一定也是形如<A>x的,那麼把它換到隊尾,顯然可以保證合法性。
    • 證明佇列Q長度可以到達 n k 。反證法,設當Q長度為m時,就找不到下一個沒有出現過的節點了。根據我們演算法的(2)(3)步驟,我們知道當隊尾元素找不到下一個節點時,我們會嘗試下一個隊首元素,如果完全無法找到下一個節點,說明Q中所有節點都無法連到下一個沒有出現過的節點了。這就說明這m個節點構成了一個聯通分量,總體至少有兩個聯通分量,這和我們一開始知道的這個圖是強聯通的,應該只有一個聯通分量矛盾。因此我們一定可以構造到 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;
    }
};