1. 程式人生 > 實用技巧 >leetcode [491. 遞增子序列] (Rabin-Karp 字串編碼)

leetcode [491. 遞增子序列] (Rabin-Karp 字串編碼)

(https://leetcode-cn.com/problems/increasing-subsequences/)

這題剛開始自己寫的時候是沒有想到用dfs的,我的想法是開一個vector<vector > dp[n] ,其中dp[i]裡面放的是以nums[i]結尾的所有子序列,這樣為了得到dp[i] ,我們只需要在掃描i前面的數,如果與nums[i] 相等或小於nums[i] ,我們就可以把那個數的所有子序列拿過來在加上nums[i]就得到了dp[i] 。

        for (int i = 0; i < n; i++){
            dp[i].push_back({nums[i]});
            for (int j = 0; j < i; j++){
                if (nums[i] >= nums[j]){
                    for (auto v : dp[j]){
                        v.push_back(nums[i]);
                        dp[i].push_back(v);
                    }
                }
            }
        }

但是這樣寫有一個問題,就是最後合併答案的時候,可能會有重複的子序列,這樣就需要解決另一個問題,如何進行序列判重?官方題解中也給出了方法:Rabin-Karp 字串編碼

Rabin-Karp 字串編碼:

這個其實思想很簡單,就將一個數列當成k進位制,k要比這個數列中最大的數大,而且最好選一個質數。在將k進位制轉化為10進位制,這樣一個數列就會對應一個hash值。

但這會存在一個問題,當數列很長的時候,這個ha sh值會很大很大,甚至longlong都裝不下,這樣自然而然就想到了取模,那問題又出現了,會不會有兩個不同的數列他們的hash值不一樣,但取模後就相等了呢?

這就涉及到概率問題了,就直接說結論:,只要我們將模數設定得很大,並且多選擇一些模數,Rabin-Karp 字串編碼產生雜湊碰撞的概率就微乎其微。一般來說,對於演算法題而言,我們只需要選擇一個模數即可,並且它最好是一個質數,例如 10^9+7。如有需要,還可以選擇第二個模數 10^9+9

(來自官方解釋:https://leetcode-cn.com/problems/longest-happy-prefix/solution/zui-chang-kuai-le-qian-zhui-by-leetcode-solution/)

知道了這個方法,我們就可以進行子序列判重了,將每個長度大於2的子序列進行hash,如果某個子序列的hash值已經存在,就說明這個子序列已經放到答案裡了,不需要重複放

//完整程式碼
const int BASE = 263;
const int MOD = 1e9+7;
class Solution {
public:
    int GetHash(vector<int> now){
        int n = now.size();
        int res = 0;
        for (int i = 0; i < n; i++){
            res = (1LL*res*BASE%MOD +(now[i]+101)) % MOD;
        }
        return res;
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return {};
        vector<vector<int>> dp[n];

        for (int i = 0; i < n; i++){
            dp[i].push_back({nums[i]});
            for (int j = 0; j < i; j++){
                if (nums[i] >= nums[j]){
                    for (auto v : dp[j]){
                        v.push_back(nums[i]);
                        dp[i].push_back(v);
                    }
                }
            }
        }

        vector<vector<int>> ans;
        map<int,bool> M;
        for (int i = 0; i < n; i++){
            for (auto v : dp[i]){
                int hash = GetHash(v);
                if (v.size() >= 2 && M.count(hash) == 0){
                    ans.push_back(v);
                    M[hash] = true;
                }
            }
        }
        return ans;
    }
};

另一種方法就是dfs,題解中說的應該也挺清楚的,這裡就直接放程式碼了

class Solution {
public:
    vector<vector<int>> ans;
    void dfs(vector<int>& nums,int begin,vector<int> cnt){
        if (cnt.size() >= 2) ans.push_back(cnt);

        map<int,bool> M;
        for (int i = begin+1; i < nums.size(); i++){
            if (begin == -1 || nums[i] >= cnt.back()){
                if (M.count(nums[i])) continue;
                M[nums[i]] = true;
                cnt.push_back(nums[i]);
                dfs(nums,i,cnt);
                cnt.pop_back();
            }
        }

    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        ans.clear();
        dfs(nums,-1,{});

        return ans;
    }
};