LeetCode第215場周賽

RJ_theMag發表於2020-11-16

第一題 1656. 設計有序流


可以開一個字串陣列存放字串,用一個額外的變數ptr存放ptr的位置,插入新的字串的時候,檢查從ptr開始是否有連續的按照id遞增的序列即可。

程式碼如下:

class OrderedStream {
public:
    int n;
    int ptr;
    vector<string> stream;
    
    OrderedStream(int _n) {
        n = _n;
        ptr = 1;                              
        stream = vector<string>(n + 1, "");                  // 字串陣列中所有字串最初都為空
    }
    
    vector<string> insert(int id, string value) {
        stream[id] = value;
        if(stream[ptr] == "") {            // 如果插入一個新字串,但id=ptr的字串為空,則返回一個空的字串陣列
            return {};
        }
        vector<string> res;
        int last = ptr;                   // last記錄更新後的ptr的位置
        for(int i = ptr; i <= n; ++i) {
            if(stream[i] != "") {      
                res.push_back(stream[i]);
            } else {                      // 找到一個空的位置,則更新last
                last = i;
                break;
            }
        }
        if(stream[last] != "") {
            ptr = last + 1;
        } else {
            ptr = last;
        }
        return res;
    }
};

/**
 * Your OrderedStream object will be instantiated and called as such:
 * OrderedStream* obj = new OrderedStream(n);
 * vector<string> param_1 = obj->insert(id,value);
 */

第二題 5603. 確定兩個字串是否接近


根據題意,兩個字串接近,則滿足這些條件:(1)兩個字串中不同的字母的個數相同;(2)如果給所有的字母統計出現次數,則如果某字串中有出現次數為x的字母y個,則另一個字串也必然有y個出現次數為x的字母;(3)第一個字串中的所有字母都在第二個單詞內出現過,反之亦然。

第(1)和第(3)點我們可以使用兩個unorderd_set進行判斷。
第(2)點可以用兩個unordered_map<char, int> cnt1, cnt2統計兩個字串中各個字母出現的頻率,然後用兩個字串(或陣列)將雜湊表中的所有頻率拼接為一個字串,如果原單詞word1和word2是“接近”的字串,則將它們的字母出現頻率拼接成的字串排序之後,這兩個排序後的字串必然是相等的,如果不相等,則它們不接近。這一點很好理解,比如word1裡有若干個單詞出現次數為:2 1 3 4, word2裡有若干個單詞出現次數為:1 2 4 3,那麼它們的出現次數拼接的單詞排序之後都是1234,所以這兩個單詞是接近的。

程式碼如下:

class Solution {
public:
    unordered_set<char> hash1, hash2;            // 計算兩個單詞中不同單詞的個數
    unordered_map<char, int> cnt1, cnt2;         // 統計兩個單詞中不同字母出現的次數
    
    bool closeStrings(string word1, string word2) {
        if(word1.size() != word2.size()) {      // 如果兩個單詞長度不同,那麼它們不接近
            return false;
        }
        for(auto &c : word1) {
            hash1.insert(c);
            ++cnt1[c];
        }
        for(auto &c : word2) {
            hash2.insert(c);
            ++cnt2[c];
        }
        for(auto &c : hash1) {
            if(hash2.count(c) == 0) {            // 如果第一個單詞有第二個單詞沒有出現過的字母,則它們不接近
                return false;
            }
        }
        for(auto &c : hash2) {
            if(hash1.count(c) == 0) {           // 如果第二個單詞有第一個單詞沒有出現過的字母,則它們不接近
                return false;
            }
        } 
        string fre1, fre2;                      // fre1和fre2分別是對word1和word2的單詞出現的頻率拼接得到的字串
        for(auto &cnt : cnt1) {
            fre1 += to_string(cnt.second);
        }
        for(auto &cnt : cnt2) {
            fre2 += to_string(cnt.second);
        }
        sort(fre1.begin(), fre1.end());
        sort(fre2.begin(), fre2.end());
        if(fre1 != fre2) {
            return false;
        }
        return true;
    }
};

第三題 5602. 將 x 減到 0 的最小運算元

題意要求在陣列的兩端不斷地刪除元素,使得刪掉的元素的和為x,求最小的刪除次數。可以反向思考:求陣列中最長的區間和為sum - x的區間,其中sum為陣列中所有元素的和。

求陣列中的區間和可以使用字首和,然而列舉區間的起點和終點時間複雜度仍會達到O(n2),對於105的區間長度是無法接受的。

因此我們可以考慮用一個雜湊表unordered_map<int, int> mp來記錄某個字首和的下標,mp[i] = j表示字首和為i的下標為j,即nums[0] + nums[1] + … nums[j] = i, 我們要找出區間和target = sum - x的區間,而我們在迴圈的時候可以知道當前位置i的字首和preSum[i], 因此我們只要知道字首和為preSum[i] - target的位置,我們就得到了滿足題意的一段區間和(也就是說,刪掉這段區間的左邊部分和右邊部分,可以滿足題意),然後我們可以用陣列元素的個數 - 這段區間的長度來更新答案。 使用雜湊表記錄字首和的下標,我們可以在O(n)的時間複雜度內求出滿足條件的區間和和區間長度。

程式碼如下:

class Solution {
public:
    vector<int> preSum;
    unordered_map<int, int> mp;  // 記錄某字首和的下標
    
    int minOperations(vector<int>& nums, int x) {
        if(nums[0] > x && nums.back() > x) {            // 如果陣列的第一個和最後一個元素都大於x,返回-1
            return -1;
        }
        int n = nums.size();
        preSum = vector<int>(n + 1, 0);
        int res = n + 1;
        mp[0] = 0;                      // 0的字首和為0
        for(int i = 1; i <= n; ++i) {
            preSum[i] = preSum[i - 1] + nums[i - 1];
            mp[preSum[i]] = i;          // i的字首和為preSum[i]
        }
        int sum = preSum[n];            // sum是陣列的所有元素的和
        if(x > sum) {
            return -1;
        }
        if(sum == x) {                  // 刪掉所有元素
            return n;
        }
        int target = sum - x;          // 要求的區間和target為sum - x
        for(int i = 1; i <= n; ++i) {
            if(mp[preSum[i] - target] == 0) {           // 之前沒記錄過字首和為preSum[i] - left的位置
                if(preSum[i] == target) {               // 如果preSum[i]正好就是target,那麼說明我們要刪掉i後面的所有元素,可以滿足刪掉的元素的和為x
                    res = min(res, n - i);
                }
            } else {                               // 之前記錄過字首和preSum[i] - target的位置,也就是說,我們找到了一個滿足條件的區間[left ~ i]
                int left = mp[preSum[i] - target];
                res = min(res, n - (i - left));    // 更新最小運算元:區間和為sum - x的區間長度為i - left, 則這段區間左邊部分與右邊部分的長度為n - (i - left)
            }
        }
        return res;
    }
};

第四題 1659. 最大化網格幸福感


狀態壓縮DP,這裡參考了坑神的視訊題解

用dp[r][i][e][s]表示前r行網格(1<=r<=n)、居住了i個內向的人(0<=i<=introvertsCount)、e個外向的人(0<=e<=extrovertsCount)、並且第r行的居住人的分佈狀態為s的最大網格幸福感,s的三進製表示是第r行的分佈。如果第r行的第j個網格(0<=j<m)沒有住人,則s的第j位為0;如果住了一個內向的人,則s的第j位為1;如果住了一個外向的人,則s的第j位為2。
則整個網格的最大幸福感是,處理了前n行,最多居住introvertsCount個內向的人、最多居住extrovertsCount個外向的人,分佈狀態為s(0<=s<3^m)的網格幸福感的最大值。

有了狀態表示,我們要考慮狀態轉移。

首先,由於我們是按行列舉,所以我們對每一行,都要考慮上一行對這一行的影響(影響指:內向的人數的變化、外向的人數的變化、幸福感的變化)。另外,由於這一行的人員分佈狀態也可能影響上一行幸福感(比如這一行住了人,會影響到上一行的內向的人和外向的人的幸福感),因此我們在列舉某行的狀態和上一行的狀態時,要考慮到互相的影響,具體的分析寫在下面的註釋裡。

程式碼如下:

int dp[7][7][7][250];      // dp[r][i][e][s]:前r行使用了i個內向的人、e個外向的人,分佈狀態為s的最大網格幸福感。 因為一行最多有5個格子,3^5=243,所以分佈狀態s的範圍為0~242
int base[6];               // base表示每一列是3的多少次冪。第0列base[0] = 3^0 = 1, 第1列base[1] = 3^1 = 3, 第2列base[2] = 3^2 = 9, 第3列base[3] = 3^3 = 27,第4列base[4] = 81, 第5列base[5] = 243。 base陣列的目的是為了方便為門計算狀態s的某一位是多少(0/1/2)
int movIn[250][250], movEx[250][250], mov[250][250];      // movIn[i][j]表示從某行的人員分佈狀態i轉移到下一行的人員分佈狀態j內向的人數的變化,movEx[i][j]表示外向的人數的變化,mov[i][j]表示幸福感的變化

class Solution {
public:
    int get(int s, int i) {                  // 計算狀態s的第i位是多少。 返回值=0:沒人    =1:內向的人         =2:外向的人
        s = s % base[i + 1];
        return s / base[i];
    }

    int getMaxGridHappiness(int m, int n, int introvertsCount, int extrovertsCount) {
        memset(dp, -1, sizeof dp);
        dp[0][0][0][0] = 0;            
        base[0] = 1;
        for(int i = 1; i <= 5; ++i) {
            base[i] = base[i - 1] * 3;
        }
        int lim = base[m];             // lim是某一行狀態的最大值+1,也就是說,某一行的狀態s的取值範圍為0 ~ lim - 1

        // 預處理出所有狀態到其他狀態造成的movIn, movEx, mov的變化
        for(int s = 0; s < lim; ++s) {                  // 列舉上一行的狀態s
            for(int cur = 0; cur < lim; ++cur) {        // 列舉當前行的狀態cur
                movIn[s][cur] = movEx[s][cur] = mov[s][cur] = 0;      // 先假設上一行的狀態轉移到當前行的狀態,內向的人數、外向的人數、幸福感都沒有變化
                for(int i = 0; i < m; ++i) {            // 列舉當前行的每一列
                    if(get(cur, i) == 0) {              // 如果當前行的第i列沒有住人
                        continue;                       // 那麼它對上一行沒有任何影響
                    } else {                                       
                        if(get(s, i) == 1) {           // 如果當前位置住了人並且上一行的這個位置住了個內向的人,那麼幸福感-30
                            mov[s][cur] -= 30;
                        } else if(get(s, i) == 2) {    // 如果當前位置住了人並且上一行的這個位置住了個外向的人,那麼幸福感+20
                            mov[s][cur] += 20;
                        }
                    }
                    if(get(cur, i) == 1) {             // 如果當前位置住了個內向的人
                        movIn[s][cur] += 1;            // 更新內向的人數的變化
                        mov[s][cur] += 120;            // 內向的人的初始幸福感是120
                        if(get(s, i) > 0) {            // 如果上一行這一列位置住了人,那麼幸福感-30
                            mov[s][cur] -= 30;
                        }
                        if(i > 0 && get(cur, i - 1) > 0) {    // 如果左邊位置住了人,幸福感-30
                            mov[s][cur] -= 30;
                        }
                        if(i + 1 < m && get(cur, i + 1) > 0) {      // 如果右邊位置住了人,幸福感-30
                            mov[s][cur] -= 30;
                        }
                    }
                    if(get(cur, i) == 2) {                 // 如果當前位置住了一個外向的人
                        movEx[s][cur] += 1;                // 更新外向的人數
                        mov[s][cur] += 40;                 // 外向的人初始幸福感是40
                        if(get(s, i) > 0) {                // 如果上一行這一列住了個人
                            mov[s][cur] += 20;             // 幸福感+20
                        }
                        if(i > 0 && get(cur, i - 1) > 0) {    // 如果左邊位置住了人,幸福感+20
                            mov[s][cur] += 20;
                        }
                        if(i + 1 < m && get(cur, i + 1) > 0) {     // 如果右邊位置住了人,幸福感+20
                            mov[s][cur] += 20;
                        }
                    }
                }
            }
        }

        // 動態規劃
        for(int r = 1; r <= n; ++r) {              // 列舉1~n行
            for(int i = 0; i <= introvertsCount; ++i) {      // 列舉內向的人的個數
                for(int e = 0; e <= extrovertsCount; ++e) {      // 列舉外向的人的個數
                    for(int s = 0; s < lim; ++s) {            // 列舉上一行(第r - 1行)的狀態s
                        if(dp[r - 1][i][e][s] == -1) {        
                            continue;
                        }
                        for(int cur = 0; cur < lim; ++cur) {     // 列舉當前行的狀態cur
                            int deltaIn = movIn[s][cur], deltaEx = movEx[s][cur], delta = mov[s][cur];      // deltaIn, deltaEx, delta表示上一行的狀態s到這一行的狀態cur引起的內向的人數、外向的人數、幸福感的變化
                            if(i + deltaIn <= introvertsCount && e + deltaEx <= extrovertsCount) {          // 如果內向的人數和外向的人數的個數分別不超過introvertsCount和extrovertsCount,則更新最大幸福感
                                dp[r][i + deltaIn][e + deltaEx][cur] = max(dp[r][i + deltaIn][e + deltaEx][cur], dp[r - 1][i][e][s] + delta);
                            }
                        }
                    }
                }
            }
        }
        int ans = 0;                  // 我們要求出最後一行(第n行)的最大幸福感,就是最終的答案
        for(int i = 0; i <= introvertsCount; ++i) {            // 列舉內向的人的個數、外向的人的個數、以及第n行的人員分佈狀態
            for(int e = 0; e <= extrovertsCount; ++e) {
                for(int s = 0; s < lim; ++s) {
                    ans = max(ans, dp[n][i][e][s]);
                }
            }
        }
        return ans;
    }
};

相關文章