LeetCode | 第 85 场双周赛-做题记录

个人博客主页:https://shansan.xyz/

博客文章原链接:https://shansan.xyz/LeetCode-85-c5d4ac27b444443a83ff8deae1967142

目录

第 85 场双周赛-链接

AC情况:✅ ✅ ✅ ❌

1. 得到 K 个黑块的最少涂色次数

解题思路:

AC代码:

2. 二进制字符串重新安排顺序需要的时间

解题思路:

AC代码

3. 字母移位 II

解题思路

AC代码

4. 删除操作后的最大子段和

解题思路❌

代码

思路学习:

AC代码

第 85 场双周赛-链接

竞赛 - 力扣 (LeetCode)

AC情况:✅ ✅ ✅ ❌

1. 得到 K 个黑块的最少涂色次数

给你一个长度为 n 下标从 0 开始的字符串 blocks ,blocks[i] 要么是 'W' 要么是 'B' ,表示第 i 块的颜色。字符 'W' 和 'B' 分别表示白色和黑色。

给你一个整数 k ,表示想要 连续 黑色块的数目。

每一次操作中,你可以选择一个白色块将它 涂成 黑色块。

请你返回至少出现 一次 连续 k 个黑色块的 最少 操作次数。

解题思路:

这是一道简单题,最初的思路是通过递归或者是动态规划去做,明显把问题想复杂了,于是卡了一会儿,最后通过移动窗口的方式去解,实际上暴力应该也可以。

给出从下标0开始长度为k的窗口,统计其中'B' 的个数cntBk-cntB就是该窗口内得到K个黑块的最少操作数,移动窗口并不断更新最小的k-cntB即得答案。

更新cntB时只需要判断窗口内移出和移入的字符状态。

  • 移出字符为'B'cntB--
  • 移入字符为'B'cntB++

复杂度:O(n)

AC代码:

class Solution {
public:
    int minimumRecolors(string blocks, int k) {
        int res = 0x7f7f7f7f, cntB = 0;
        for(int i = 0; i < k; i ++)
            if(blocks[i] == 'B') cntB ++;
        if(cntB >= k) return 0;
        res = min(res, k - cntB);
        for(int i = 1; i <= blocks.size()-k; i ++) {
            if(blocks[i-1] == 'B') cntB --;
            if(blocks[i+k-1] == 'B') cntB ++;
            if(cntB >= k) return 0;
            res = min(res, k - cntB);
        }
        return res;
    }
};

2. 二进制字符串重新安排顺序需要的时间

给你一个二进制字符串 s 。在一秒之中,所有 子字符串 "01" 同时 被替换成 "10" 。这个过程持续进行到没有 "01" 存在。

请你返回完成这个过程所需要的秒数。

解题思路:

朴素的思路,按照题目模拟,每次都遍历查找出字符中的所有的"01"并替换为"10",直到没有 "01" 存在

AC代码

class Solution {
public:
    int secondsToRemoveOccurrences(string s) {
        string str01 = "01";
        int res = 0;
        while(s.find(str01) != -1) {
            for(int i = 0; i < s.size()-1; i ++) 
                if(s[i] == '0' && s[i+1] == '1') 
                    swap(s[i], s[i+1]), i ++;
            res ++;
        }
        return res;
    }
};

3. 字母移位 II

给你一个小写英文字母组成的字符串 s 和一个二维整数数组 shifts ,其中 shifts[i] = [starti, endi, directioni] 。对于每个 i ,将 s 中从下标 starti 到下标 endi (两者都包含)所有字符都进行移位运算,如果 directioni = 1 将字符向后移位,如果 directioni = 0 将字符向前移位。

将一个字符 向后 移位的意思是将这个字符用字母表中 下一个 字母替换(字母表视为环绕的,所以 'z' 变成 'a')。类似的,将一个字符 向前 移位的意思是将这个字符用字母表中 前一个 字母替换(字母表是环绕的,所以 'a' 变成 'z' )。

请你返回对 s 进行所有移位操作以后得到的最终字符串。

解题思路

这道题又是一道模拟题,按照题目意思模拟即可。最朴素的写法就是定义出题中的shift方法,并不断按照shifts[i]处理字符串,这样的复杂度是$O(n^2)$

string shiftStr(string str, vector<int>& shift) {
        int left = shift[0], right = shift[1], op = shift[2];
        if(op == 0) { //向前
            for(int i = left; i <= right; i ++) {
                if(str[i] == 'a') str[i] = 'z';
                else str[i] -= 1;
            }
        } else { //向后
            for(int i = left; i <= right; i ++) {
                if(str[i] == 'z') str[i] = 'a';
                else str[i] += 1;
            }
        }
        return str;
    }

题目中 1 <= s.length, shifts.length <= 5 * 10^4,平方后大概在$10^825 = 2.510^9$,不出所料的超时了。

于是想优化方法,题中字符的移位操作都是按照数组更新的,于是很容易就可以想到用差分来降低复杂度。

给定一个opV用来记录所有的移位操作,向前移位定义为-1,向后移位定义为+1,在所有移位操作记录完毕后,累加得到真正的移位数组,然后处理对应的移位操作,需要注意特殊情况和边界。

复杂度:O(n)

AC代码

class Solution {
public: 
    string shiftingLetters(string s, vector<vector<int>>& shifts) {
        vector<int> opV(s.size()+10, 0);
        for(int i = 0; i < shifts.size(); i ++) {
            int left = shifts[i][0], right = shifts[i][1], op = shifts[i][2];
            //向前-1, 向后+1
            if(op == 0) opV[left] --, opV[right+1] ++;
            else opV[left] ++, opV[right+1] --;
        }
        
        for(int i = 0; i < s.size(); i ++) {
            if(i != 0) opV[i] += opV[i-1];
        }
        
        for(int i = 0; i < s.size(); i ++) {
            if(opV[i] < 0) {
                opV[i] = -opV[i], opV[i] %= 26;
                int t = s[i] - 'a';
                if(t < opV[i]) s[i] = 'z' - (opV[i] - t - 1);
                else s[i] -= opV[i];
            } else {
                opV[i] %= 26;
                int t = 'z' - s[i];
                if(t < opV[i]) s[i] = 'a' + (opV[i] - t - 1);
                else s[i] = s[i] + opV[i];
            }
        }
        return s;
    }
};

4. 删除操作后的最大子段和

给你两个下标从 0 开始的整数数组 nums 和 removeQueries ,两者长度都为 n 。对于第 i 个查询,nums 中位于下标 removeQueries[i] 处的元素被删除,将 nums 分割成更小的子段。

一个 子段 是 nums 中连续  整数形成的序列。子段和 是子段中所有元素的和。

请你返回一个长度为 n 的整数数组 answer ,其中 answer[i]是第 i 次删除操作以后的 最大 子段和。

注意:一个下标至多只会被删除一次。

解题思路❌

这道题我的思路也是按照题意模拟,为了方便,逆向转换,把删除操作改为添加操作,使用node(val, ldx, rdx)来记录子数组的边界和子段和,遍历nodeval,即可获得最大子段和,且每一次加入新节点后,都利用栈对相邻的子段进行合并,反转得到的最大子段和数组得到的就是每次删除操作的最大子段和。

复杂度:O(n^2)

代码

27 / 44 个通过测试用例

class Solution {
public:
    struct node{
      long val;
      int ldx, rdx;  
    };
    
    static bool cmp(node a, node b) {
        return a.ldx < b.ldx;
    }
    
    vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) {
        vector<long long> res;
        res.push_back(0);
        vector<node> nodeList;
        for(int i = removeQueries.size()-1; i >= 0; i --) {
            long long tempMax = 0;
            int flag = 0, curIdx = removeQueries[i];
            if(nodeList.empty()) nodeList.push_back({nums[curIdx], curIdx, curIdx}), flag = 1;
            for(int j = 0; j < nodeList.size(); j ++) {
                if(tempMax < nodeList[j].val) tempMax = nodeList[j].val;
                if(!flag) {
                    if(nodeList[j].ldx == curIdx+1) {
                        nodeList[j].ldx = curIdx, nodeList[j].val += nums[removeQueries[i]], flag = 1;
                    } else if(nodeList[j].rdx == curIdx-1) {
                        nodeList[j].rdx = curIdx, nodeList[j].val += nums[removeQueries[i]], flag = 1;
                    }
                }
            }
            if(!flag) {
                nodeList.push_back({nums[curIdx], curIdx, curIdx});
            }
            if(i != removeQueries.size() - 1) res.push_back(tempMax);
            //merge nodes
            sort(nodeList.begin(), nodeList.end(), cmp);
            vector<node> newNodeList;
            for(int j = 0; j < nodeList.size(); j ++) {
                if(j == 0) newNodeList.push_back(nodeList[0]);
                else {
                    if(newNodeList.back().rdx + 1 == nodeList[j].ldx) {
                        newNodeList[newNodeList.size()-1].rdx = nodeList[j].rdx;
                        newNodeList[newNodeList.size()-1].val += nodeList[j].val;
                    } else {
                        newNodeList.push_back(nodeList[j]);
                    }
                }
            }
            nodeList = newNodeList;
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

思路学习:

看了其他人的解法,思路也都是逆向增加+合并

合并操作使用并查集来简化,果然还是写的题太少,做题时完全没有想到这个方法

复杂度:O(nlogn)

AC代码

class Solution {
private:
    const static int N = 1e5+10;
    int p[N];
    long long val[N];
public:
    int find(int a) {
        if(p[a] != a) p[a] = find(p[a]);
        return p[a];
    }
    vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) {
        iota(p, p+N, 0);
        memset(val, 0, sizeof(val));
        int len = nums.size();
        vector<long long> res(len);
        for(int i = len-1; i > 0; i --) {
            int numIdx = removeQueries[i];
            val[numIdx] = nums[numIdx];
            if(numIdx - 1 >= 0 && val[numIdx-1]) { //向前merge
                int t = find(numIdx-1);
                p[t] = numIdx; //以numIdx为根节点
                val[numIdx] += val[t];
            } 
            if(numIdx + 1 < len && val[numIdx+1] ) { //向后merge
                int t = find(numIdx+1);
                p[t] = numIdx; //以numIdx为根节点
                val[numIdx] += val[t];
            }
            res[i-1] = max(res[i], val[numIdx]);
        }
        return res;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值