leetcode biweekly contest 85
双周赛的质量又好起来了,前几周的题目质量水平下降严重,本周开始质量好很多了。
6156. 得到 K 个黑块的最少涂色次数
题目
给你一个长度为 n
下标从 0
开始的字符串 blocks
,blocks[i]
要么是 'W'
要么是 'B'
,表示第i
块的颜色。字符'W'
和'B'
分别表示白色和黑色。
给你一个整数 k
,表示想要 连续 黑色块的数目。
每一次操作中,你可以选择一个白色块将它 涂成 黑色块。
请你返回至少出现 一次 连续 k
个黑色块的 最少 操作次数。
示例 1:
输入:blocks = "WBBWWBBWBW", k = 7 |
示例 2:
输入:blocks = "WBWBBBW", k = 2 |
提示:
n == blocks.length
1 <= n <= 100
blocks[i]
要么是'W'
,要么是'B'
。1 <= k <= n
地址
https://leetcode.cn/problems/minimum-recolors-to-get-k-consecutive-black-blocks
题意
滑动窗口
思路
- 直接采用滑动窗口找到 $k$ 个长度 中黑色方块 ,找到连续的 $k$ 个长度中白色块最少的即可。
- 复杂度分析:
- 时间复杂度:$O(n)$,其中 $n$ 表示字符串的长度。
- 空间复杂度:$O(1)$。
代码
- 滑动窗口
class Solution {
public:
int minimumRecolors(string blocks, int k) {
int n = blocks.size();
int ans = n;
int whiteCnt = 0, blackCnt = 0;
for (int i = 0; i < n; i++) {
if (blocks[i] == 'W') {
whiteCnt++;
}
if (i >= k - 1) {
ans = min(ans, whiteCnt);
if (blocks[i - k + 1] == 'W') {
whiteCnt--;
}
}
}
return ans;
}
};
6157. 二进制字符串重新安排顺序需要的时间
题目
给你一个二进制字符串 s
。在一秒之中,所有 子字符串 "01"
同时 被替换成 "10"
。这个过程持续进行到没有 "01"
存在。
请你返回完成这个过程所需要的秒数。
示例 1:
输入:s = "0110101" |
示例 2:
输入:s = "11100" |
提示:
1 <= s.length <= 1000
s[i]
要么是'0'
,要么是'1'
。
地址
https://leetcode.cn/problems/time-needed-to-rearrange-a-binary-string
题意
直接模拟或者思维
思路
- 直接模拟就非常简单,我们可以观察到直接模拟的时间复杂度不超过 $n^2$,因此我们可以直接模拟即可,知道字符串中没有字符串
01
出现即可。 - 动态规划,本质上讲右侧的 $1$ 往左侧移动,如果 $1$ 前面有连续的 $0$ ,则可以讲其一直向左移动直到左侧为 $1$ 为止,此时需要等待。
- 复杂度分析:
- 时间复杂度:时间复杂度为 $O(n)$,$n$ 表示节点的数目。
- 空间复杂度:$O(n)$,$n$ 表示节点的数目。
代码
- 直接模拟:
class Solution { |
动态规划:
class Solution {
public:
int secondsToRemoveOccurrences(string s) {
int n = s.size();
int ans = 0, cnt = 0;
for (int i = 0; i < n; i++) {
if(s[i] == '0') {
++cnt;
} else {
if (cnt > 0) {
ans = max(ans + 1, cnt);
}
}
}
return ans;
}
};
6158. 字母移位 II
题目
给你一个小写英文字母组成的字符串 s
和一个二维整数数组 shifts
,其中 shifts[i] = [starti, endi, directioni]
。对于每个 i
,将 s
中从下标 starti
到下标 endi
(两者都包含)所有字符都进行移位运算,如果 directioni = 1
将字符向后移位,如果 directioni = 0
将字符向前移位。
将一个字符 向后 移位的意思是将这个字符用字母表中 下一个 字母替换(字母表视为环绕的,所以 'z'
变成 'a'
)。类似的,将一个字符 向前 移位的意思是将这个字符用字母表中 前一个 字母替换(字母表是环绕的,所以 'a'
变成 'z'
)。
请你返回对 s
进行所有移位操作以后得到的最终字符串。
示例 1:
输入:s = "abc", shifts = [[0,1,0],[1,2,1],[0,2,1]] |
示例 2:
输入:s = "dztz", shifts = [[0,0,0],[1,1,1]] |
提示:
1 <= s.length, shifts.length <= 5 * 104
shifts[i].length == 3
0 <= starti <= endi < s.length
0 <= directioni <= 1
s
只包含小写英文字母。
地址
https://leetcode.cn/contest/biweekly-contest-85/problems/shifting-letters-ii/
题意
差分数组或者线段树
思路
- 线段树的解法就非常暴力,没啥好讲的,需要
lazy
节点之类的,比较麻烦。查分数组的思路就非常简单,直接讲每个区间 $[l,r]$ ,在 $l,r+1$ 处进行进行标记即可,统计时依次统计即可。
- 时间复杂度:时间复杂度为 $O(m + n)$,其中 $m$ 为 $shift$ 数组的长度,$n$ 为字符串的长度。
- 空间复杂度:空间复杂度为 $O(n)$,其中 $n$ 表示字符串的长度。
代码
class Solution { |
6159. 删除操作后的最大子段和
题目
给你两个下标从 0 开始的整数数组 nums
和 removeQueries
,两者长度都为 n
。对于第 i
个查询,nums
中位于下标 removeQueries[i]
处的元素被删除,将 nums
分割成更小的子段。
一个 子段 是 nums
中连续 正 整数形成的序列。子段和 是子段中所有元素的和。
请你返回一个长度为 n
的整数数组 answer
,其中 answer[i]
是第 i
次删除操作以后的 最大 子段和。
注意:一个下标至多只会被删除一次。
示例 1:
输入:nums = [1,2,5,6,1], removeQueries = [0,3,2,4,1] |
示例 2:
输入:nums = [3,2,11,1], removeQueries = [3,2,1,0] |
提示:
n == nums.length == removeQueries.length
1 <= n <= 105
1 <= nums[i] <= 109
0 <= removeQueries[i] < n
removeQueries
中所有数字 互不相同 。
地址
https://leetcode.cn/contest/biweekly-contest-85/problems/maximum-segment-sum-after-removals/
题意
二分查找或者并查集
思路
- 直接模拟的思路非常简单,也非常暴力。直接按照题目思路要求,每次将分段进行拆分即可。我们用一个
treemap
保存所有的分段信息,用一个treemap
保存当前所有数组的中的分段和的统计。每次我们在treemap
中查找给定的区间 $[x,x]$ 匹配的分段 $[l,r]$ ,然后将分段拆分成 $[l,x-1],[x+1,r]$ ,并从map
中删除分段 $[l,r]$, 在treemap
删除 $sum[l,r]$, 并同时添加 $sum[l,x-1], sum[x+1,r]$, 非常简单的模拟即可。
时间复杂度:$O(m \log m)$,其中 $m$ 表示数组的长度。
空间复杂度:$O(m)$,其中 $m$ 表示数组的长度。
- 典型的并查集的操作。我们从后往前恢复数组,就可以看到实际为该数组的恢复过程,此时就联想到用并查集,思路非常简单,感觉是个经典题目,虽然不是特别难的题目。每次恢复 $x$ 时:
- 查看 $x$ 的左边位置 $x-1$ 是否已经恢复,如果已经恢复,则将两个位置进行合并;
- 查看 $x$ 的右边位置 $x+1$ 是否已经恢复,如果已经恢复,则将两个位置进行合并;
- 每次查询集合 $x$ 所在的集合的元素的和,更新当前的最大值即可;
时间复杂度分析:
- 时间复杂度:$O(n \times \alpha(n))$,其中 $n$ 表示数组的长度。
- 空间复杂度:$O(n)$,其中 $n$ 表示数组的长度。
代码
- 二分查找
class Solution {
public:
vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) {
int n = nums.size();
vector<long long> sum(n + 1);
for (int i = 0; i < n; i++) {
sum[i+1] = sum[i] + nums[i];
}
map<pair<int,int>, long long> cnt;
map<long long, int> now;
cnt[make_pair(n-1, 0)] = sum[n];
now[sum[n]]++;
int m = removeQueries.size();
vector<long long> ans(m);
for (int i = 0; i < m - 1; i++) {
int x = removeQueries[i];
auto it = cnt.lower_bound({x,0});
int r = it->first.first;
int l = it->first.second;
long long s = it->second;
cnt.erase(it);
now[s]--;
if(now[s] == 0) {
now.erase(s);
}
if (x > l) {
cnt[make_pair(x - 1, l)] = sum[x] - sum[l];
now[sum[x] - sum[l]]++;
}
if (x < r) {
cnt[make_pair(r, x + 1)] = sum[r+1] - sum[x+1];
now[sum[r+1] - sum[x+1]]++;
}
ans[i] = now.rbegin()->first;
}
return ans;
}
}; - 并查集
class Solution {
public:
int find(vector<int> &f, int x) {
while (x != f[x]) {
x = f[x];
}
return x;
}
void uni(vector<int> &f, vector<long long> &sum, int x, int y) {
int fx = find(f, x);
int fy = find(f, y);
f[fx] = fy;
sum[fy] += sum[fx];
}
vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) {
int n = nums.size();
long long curr = 0;
vector<int> fa(n, -1);
vector<long long> sum(n, 0);
vector<long long> ans(n, 0);
for (int i = n - 1; i >= 0; i--) {
ans[i] = curr;
int x = removeQueries[i];
fa[x] = x;
sum[x] = nums[x];
if (x - 1 >= 0 && fa[x - 1] >= 0) {
uni(fa, sum, x, x - 1);
}
if (x + 1 < n && fa[x + 1] >= 0) {
uni(fa, sum, x, x + 1);
}
curr = max(curr, sum[find(fa, x)]);
}
return ans;
}
};
欢迎关注和打赏,感谢支持!
- 关注我的博客: http://mikemeng.org/
- 关注我的知乎:https://www.zhihu.com/people/da-hua-niu
- 关注我的微信公众号: 公务程序猿
扫描二维码,分享此文章