zoukankan      html  css  js  c++  java
  • LeetCode第215场周赛

    第一题 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;
        }
    };
    
  • 相关阅读:
    Linux三剑客之sed
    xcodebuild
    mac xcworkspace xcodebuild
    [转]Jenkins Xcode打包ipa
    ios批量打包
    ios打包
    ios 打包
    ios 尺寸
    Launch Screen在iOS7/8中的实现
    如何查看ipa包支持哪些cpu指令集
  • 原文地址:https://www.cnblogs.com/linrj/p/13989749.html
Copyright © 2011-2022 走看看