1. 程式人生 > 實用技巧 >LeetCode周賽#204 題解

LeetCode周賽#204 題解

1566. 重複至少 K 次且長度為 M 的模式 #模擬

題目連結

題意

給定正整數陣列 arr,請你找出一個長度為 m 且在陣列中至少重複 k 次的模式。

模式 是由一個或多個值組成的子陣列(連續的子序列),連續 重複多次但 不重疊 。 模式由其長度和重複次數定義。如果陣列中存在至少重複 k 次且長度為 m 的模式,則返回 true ,否則返回 false

分析

題目初看有點麻煩,但實際上隔著特定週期(即重複長度)去檢查相應字元是否相等,模擬一下題意即可。

class Solution {
public:
    bool containsPattern(vector<int>& arr, int m, int k) {
        int cnt = 0; //統計重複度(次數)
        int len = arr.size();
        int tmp = 0; //計算當前重複長度
        for (int i = 0; i < len; i++){
            if(tmp == m){ //達到重複長度,答案+1
                cnt++;
                tmp = 0; //重複長度歸零
            }
            if(i + m >= len) break; //越界
            if(arr[i] == arr[i + m]) tmp++; 
            else{ //字元不匹配
                if(cnt >= k - 1) return true;
                cnt = 0; tmp = 0;
            }
        }
        return cnt >= k - 1;
    }
};

1567. 乘積為正數的最長子陣列長度 #貪心

題目連結

題意

給定整數陣列 nums ,請你求出乘積為正數的最長子陣列的長度。其中,一個數組的子陣列是由原陣列中零個或者更多個連續數字組成的陣列。

分析

我的版本(程式碼略長)

class Solution {
public:
    int getMaxLen(vector<int>& nums) {
        int len = nums.size();
        int cnt = 0, mymax = -0x3f3f3f3f;
        bool last = false; //記錄前面遍歷過的序列中是否存在負數
        /*從左至右遍歷*/
        for (int i = 0; i < len; i++){
            cnt++;
            if(nums[i] == 0){
                cnt = 0; last = false; 
            }
            else if(nums[i] < 0){
                if(last == false) last = true; //如果前面遍歷過的序列中並沒有過剩的負數
                else last = false;
            }
            if(last == false) mymax = max(mymax, cnt); //只有當前面的負號全消掉的時候方能更新答案
        }
        /*從右至左遍歷*/
        cnt = 0; last = false;
        for(int i = len - 1; i >= 0; i--){
            cnt++;
            if(nums[i] == 0){
                cnt = 0; last = false;
            }
            else if(nums[i] < 0){
                if(last == false)  last = true;
                else last = false;
            }
            if(last == false) mymax = max(mymax, cnt);
        }
        return (mymax == -0x3f3f3f3f) ? cnt - 1 : mymax;  //避免全為負數
    }
};

\(@xu012\)的佇列版本,易寫,不過複雜度有點高

class Solution {
public:
    int getMaxLen(vector<int>& nums) {
        int len = nums.size();
        int last = -1, ans = -1;
        queue<int> myque; //佇列存放負數位置
        for (int i = 0; i < len; i++){
            if(nums[i] == 0){ //遇到0即隔斷
                last = i;
                queue<int> tmp;
                myque.swap(tmp); //清空佇列
            }
            else if(nums[i] < 0) myque.push(i);
            if(myque.size() % 2 == 0)
                ans = max(ans, i - last); //當前數字下標-上一個0的下標
            else
                ans = max(ans, i - myque.front());
        }
        return ans;
    }
};

1569. 將子陣列重新排序得到同一個二叉查詢樹的方案數 #分治 #組合數

題目連結

題意

給定陣列 nums表示 1 到 n 的一個排列。按照元素在nums中的順序依次插入一個初始為空的二叉查詢樹。請你統計將 nums 重新排序後,統計滿足如下條件的方案數:重排後得到的二叉查詢樹與 nums 原本數字順序得到的二叉查詢樹相同,將結果對 10^9 + 7 取餘數。

樣例

分析

顯然第一個插入的元素必為根節點,設為rt,通過rt的大小,我們就能決定陣列後面的元素如何劃分至左右子樹。我們設lo表示小於rt的元素構成的集合,hi表示大於rt元素集合,顯然lohi分別代表左、右子樹節點集。

觀察到,無論我先從lo集合中取元素插入rt左子樹,還是我先從hi集合取元素插入右子樹,樹的拓撲結構並不影響,簡單來說,兩個大集合誰先誰後插入搜尋樹中,都不會發生相互影響。

那麼,集合內部的元素插入順序是否影響呢?我們回想第一句話,第一個插入元素必為根節點。改變了第一個插入的元素,就會使得新樹的結構不再於給定的原樹結構相同了。

我們只要保證左子樹的元素內部相對順序,以及右子樹的元素內部相對順序,調整兩種集合的插入順序,能夠獲得新插入序列,但不影響原樹的結構。

如何統計?從n個元素中將k個元素(均屬於某一集合)挑選出來,同時保證這k個元素相對順序相同(需要去序),共有\(C^{k}_n\)(或\(C^{lo.size}_{lo.size+hi.size}\))個組合。當然我們只是將兩個大集合的組合統計了,大集合內部同樣有如此的統計方式,由乘法原理得到\(f(n) = f(lo)\times f(hi) \times C^{lo.size}_{lo.size+hi.size}\)

typedef long long ll;
ll c[1010][1010];
class Solution {
private:
    const int MOD = 1e9+7; 
public:
    void Calculate(int len){ //預計算組合數公式
        for(int i = 1; i <= len; i++){
            c[i][0] = c[i][i] = 1;
            for(int j = 1; j < i; j++)
                c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % MOD;
        }   //遞推過程類似於01揹包
    }
    int dfs(vector<int>& arr){
        vector<int> lo, hi;
        if(arr.size() <= 1) return 1;
        for (int i = 1; i < (int)arr.size(); i++){
            if(arr[i] < arr[0]) lo.push_back(arr[i]);
            else hi.push_back(arr[i]);
        } //劃分左右子樹
        ll cur = c[lo.size() + hi.size()][lo.size()];
        cur = (cur * dfs(lo)) % MOD;
        cur = (cur * dfs(hi)) % MOD;
        return cur % MOD;
    }
    int numOfWays(vector<int>& nums) {
        Calculate(nums.size());
        return (dfs(nums) - 1 + MOD) % MOD; //記得-1
    }
};

1568. 使陸地分離的最少天數 #Tarjan求割點

題目連結

題意

給你一個由若干 01 組成的二維網格 grid ,其中 0 表示水,而 1 表示陸地。島嶼由水平方向或豎直方向上相鄰的 1 (陸地)連線形成。

如果 恰好只有一座島嶼 ,則認為陸地是 連通的 ;否則,陸地就是 分離的 。一天內,可以將任何單個陸地單元(1)更改為水單元(0)。現要求使陸地分離的最少天數。

分析

Tarjan割點的演算法我還沒學,暫時先咕

其實該題有個漏洞,所有島嶼都有小邊角,只要將小邊角切出一塊即可,也就說最多天數為2。