LC 2031. 1 比 0 多的子数组个数
题目描述
这是 LeetCode 上的 2031. 1 比 0 多的子数组个数 ,难度为 中等。
给你一个只包含 $0$ 和 $1$ 的数组 $nums$,请返回 $1$ 的数量 大于 $04 的数量的子数组的个数。
由于答案可能很大,请返回答案对 $10^9 + 7$ 取余 的结果。
一个 子数组 指的是原数组中连续的一个子序列。
示例 1:1
2
3
4
5
6
7
8
9
10输入: nums = [0,1,1,0,1]
输出: 9
解释:
长度为 1 的、1 的数量大于 0 的数量的子数组有: [1], [1], [1]
长度为 2 的、1 的数量大于 0 的数量的子数组有: [1,1]
长度为 3 的、1 的数量大于 0 的数量的子数组有: [0,1,1], [1,1,0], [1,0,1]
长度为 4 的、1 的数量大于 0 的数量的子数组有: [1,1,0,1]
长度为 5 的、1 的数量大于 0 的数量的子数组有: [0,1,1,0,1]
示例 2:1
2
3
4
5
6输入: nums = [0]
输出: 0
解释:
没有子数组的 1 的数量大于 0 的数量。
示例 3:1
2
3
4
5
6输入: nums = [1]
输出: 1
解释:
长度为 1 的、1 的数量大于 0 的数量的子数组有: [1]
提示:
- $1 <= nums.length <= 10^5$
- $0 <= nums[i] <= 1$
树状数组
为了方便,我们调整数组 $nums$ 下标从 $1$ 开始。同时将 $nums[i] = 0$ 的值看作 $nums[i] = -1$,并预处理出前缀和数组 $sum$。对于任意 $sum[i]$ 而言,其值域范围为 $[-n, n]$,我们可以通过对 $sum[i]$ 做整体 $n + 1$ 的偏移,将值域映射到 $[1, 2 * n + 1]$。
对于任意一个子数组 $i…j$ 而言,如果满足 $1$ 的数量大于 $0$,则必然有 $sum[j] - sum[i - 1] > 0$。
因此在求解以 $nums[j]$ 为右端点的「满足 $1$ 数量大于 $0$ 数量」的子数组个数时,等价于在问 $[0, j - 1]$ 范围内有多少个下标 $i$ 满足 $sum[i] < sum[j]$(即有多少个下标可以作为左端点)。
求解比 $x$ 小的数有多少个,可以使用「树状数组」来做。
具体的,我们可以遍历每个位置,假设当前处理到的位置是 $i$,其前缀和值 $t = sum[i]$,我们可以通过查询小于等于 $t - 1$ 的值数量来得知以 $i$ 为右端点的合法子数组数量,统计后,我们需要对数值为 $t$ 出现次数进行加一。
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class Solution {
int N = 200010, MOD = (int)1e9+7;
int[] tr = new int[N];
int n;
int lowbit(int x) {
return x & -x;
}
void add(int x, int v) {
for (int i = x; i <= 2 * n + 1; i += lowbit(i)) tr[i] += v;
}
int query(int x) {
int ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) ans += tr[i];
return ans;
}
public int subarraysWithMoreZerosThanOnes(int[] nums) {
n = nums.length;
int ans = 0;
int[] sum = new int[n + 10];
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (nums[i - 1] == 0 ? -1 : 1);
for (int i = 0; i <= n; i++) {
int t = sum[i] + n + 1;
ans = (ans + query(t - 1)) % MOD;
add(t, 1);
}
return ans;
}
}
- 时间复杂度:预处理前缀和数组的复杂度为 $O(n)$;查询和插入的复杂度均为 $O(\log{n})$,整体复杂度为 $O(n\log{n})$
- 空间复杂度:$O(n)$
最后
这是我们「刷穿 LeetCode」系列文章的第 No.2031
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!