本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
给你一个长度为 n
、下标从 1 开始的二进制字符串,所有位最开始都是 0
。我们会按步翻转该二进制字符串的所有位(即,将 0
变为 1
)。
给你一个下标从 1 开始的整数数组 flips
,其中 flips[i]
表示对应下标 i
的位将会在第 i
步翻转。
二进制字符串 前缀一致 需满足:在第 i
步之后,在 闭 区间 [1, i]
内的所有位都是 1 ,而其他位都是 0 。
返回二进制字符串在翻转过程中 前缀一致 的次数。
示例 1:
输入:flips = [3,2,4,1,5]
输出:2
解释:二进制字符串最开始是 "00000" 。
执行第 1 步:字符串变为 "00100" ,不属于前缀一致的情况。
执行第 2 步:字符串变为 "01100" ,不属于前缀一致的情况。
执行第 3 步:字符串变为 "01110" ,不属于前缀一致的情况。
执行第 4 步:字符串变为 "11110" ,属于前缀一致的情况。
执行第 5 步:字符串变为 "11111" ,属于前缀一致的情况。
在翻转过程中,前缀一致的次数为 2 ,所以返回 2 。
示例 2:
输入:flips = [4,1,2,3]
输出:1
解释:二进制字符串最开始是 "0000" 。
执行第 1 步:字符串变为 "0001" ,不属于前缀一致的情况。
执行第 2 步:字符串变为 "1001" ,不属于前缀一致的情况。
执行第 3 步:字符串变为 "1101" ,不属于前缀一致的情况。
执行第 4 步:字符串变为 "1111" ,属于前缀一致的情况。
在翻转过程中,前缀一致的次数为 1 ,所以返回 1 。
提示:
n == flips.length
1 <= n <= 5 * 10^4
flips
是范围 [1, n]
中所有整数构成的一个排列思考如下:由于 [ 1 , i ] [1,i] [1,i] 内恰好有 i i i 个整数( i ≥ 1 i \ge 1 i≥1),如果第 i i i 步后「前缀一致」,说明前 i i i 步中的 f l i p s [ 0 , i ) flips[0,\ i) flips[0, i) 恰好组成了 1 1 1 到 i i i 的所有数字。由于每一步只能翻转一个位置,此时必然满足「其他位都是 0 0 0 」的要求。(例如示例 1 的前 4 4 4 个数和前 5 5 5 个数。)
于是问题转换为:如何判断在第 i i i 步是否找到了 [ 1 , i ] [1,i] [1,i] 内的所有整数呢?
设 s u m sum sum 为和,在第 i i i 步我们加上当前的 f l i p s [ i ] flips[i] flips[i] 。如果 s u m sum sum 等于 i × ( i + 1 ) 2 \dfrac{i \times (i +1)} { 2} 2i×(i+1) ,则说明在第 i i i 步找到了 [ 1 , i ] [1, i] [1,i] 内的所有整数。
class Solution {
public:
int numTimesAllBlue(vector<int>& flips) {
int ans = 0;
long long sum = 0;
for (int i = 1, n = flips.size(); i <= n; ++i) {
sum += flips[i - 1];
if (sum == (long long)i * (i + 1) / 2) ++ans;
}
return ans;
}
};
复杂度分析:
由于题目保证「 flips \textit{flips} flips 是范围 [ 1 , n ] [1,n] [1,n] 中所有整数构成的一个排列」,所以前 i i i 个数互不相同。如果 f l i p s flips flips 前 i i i 个数的最大值等于 i i i ,则说明找到了 [ 1 , i ] [1,i] [1,i] 内的所有整数(如果 [ 1 , i ] [1, i] [1,i] 中有一个数没找到,那最大值必然大于 i i i ,与最大值等于 i i i 矛盾。)
遍历 flips \textit{flips} flips ,维护前 i i i 个数的最大值 mx \textit{mx} mx 。如果 mx = i + 1 \textit{mx} = i+1 mx=i+1 就把答案加一(注意代码中的数组下标需要从 0 0 0 开始,而题目描述是从 1 1 1 开始的)。
class Solution {
public int numTimesAllBlue(int[] flips) {
int ans = 0, mx = 0;
for (int i = 0; i < flips.length; ++i) {
mx = Math.max(mx, flips[i]);
if (mx == i + 1) ++ans;
}
return ans;
}
}
复杂度分析:
本题还可用树状数组来求解,虽然时间复杂度较高。因为题目的本质是第 i i i 步后,查询数组区间 [ 1 , i ] [1, i] [1,i] 中 1 1 1 的个数是否等于 i i i ,或者说 [ 1 , i ] [1, i] [1,i] 区间的和是否等于 i i i 。加上单点的翻转操作(单点修改元素从 0 0 0 到 1 1 1 ),很容易想到树状数组来维护动态区间和。
class Solution {
private:
vector<int> tree;
int n;
int lowbit(int i) { return i & -i; }
void add(int i, int v) {
while (i <= n) {
tree[i] += v;
i += lowbit(i);
}
}
int sum(int i) {
int ans = 0;
while (i) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
public:
int numTimesAllBlue(vector<int>& flips) {
n = flips.size();
tree.resize(n + 1);
int ans = 0;
for (int i = 0; i < n; ++i) {
add(flips[i], 1);
if (sum(i + 1) == i + 1) ++ans;
}
return ans;
}
};
复杂度分析: