Skip to content

Commit dd75bab

Browse files
committed
✨feat: Add 2031
1 parent eca3241 commit dd75bab

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[2031. 1 比 0 多的子数组个数]()** ,难度为 **中等**
4+
5+
Tag : 「树状数组」、「前缀和」
6+
7+
8+
9+
给你一个只包含 $0$ 和 $1$ 的数组 $nums$,请返回 $1$ 的数量 大于 $04 的数量的子数组的个数。
10+
11+
由于答案可能很大,请返回答案对 $10^9 + 7$ 取余 的结果。
12+
13+
一个 子数组 指的是原数组中连续的一个子序列。
14+
15+
示例 1:
16+
```
17+
输入: nums = [0,1,1,0,1]
18+
19+
输出: 9
20+
21+
解释:
22+
长度为 1 的、1 的数量大于 0 的数量的子数组有: [1], [1], [1]
23+
长度为 2 的、1 的数量大于 0 的数量的子数组有: [1,1]
24+
长度为 3 的、1 的数量大于 0 的数量的子数组有: [0,1,1], [1,1,0], [1,0,1]
25+
长度为 4 的、1 的数量大于 0 的数量的子数组有: [1,1,0,1]
26+
长度为 5 的、1 的数量大于 0 的数量的子数组有: [0,1,1,0,1]
27+
```
28+
示例 2:
29+
```
30+
输入: nums = [0]
31+
32+
输出: 0
33+
34+
解释:
35+
没有子数组的 1 的数量大于 0 的数量。
36+
```
37+
示例 3:
38+
```
39+
输入: nums = [1]
40+
41+
输出: 1
42+
43+
解释:
44+
长度为 1 的、1 的数量大于 0 的数量的子数组有: [1]
45+
```
46+
47+
提示:
48+
* $1 <= nums.length <= 10^5$
49+
* $0 <= nums[i] <= 1$
50+
51+
---
52+
53+
### 树状数组
54+
55+
为了方便,我们调整数组 $nums$ 下标从 $1$ 开始。同时将 $nums[i] = 0$ 的值看作 $nums[i] = -1$,并预处理出前缀和数组 $sum$。对于任意 $sum[i]$ 而言,其值域范围为 $[-n, n]$,我们可以通过对 $sum[i]$ 做整体 $n + 1$ 的偏移,将值域映射到 $[1, 2 * n + 1]$。
56+
57+
对于任意一个子数组 $i...j$ 而言,如果满足 $1$ 的数量大于 $0$,则必然有 $sum[j] - sum[i - 1] > 0$。
58+
59+
因此在求解以 $nums[j]$ 为右端点的「满足 $1$ 数量大于 $0$ 数量」的子数组个数时,等价于在问 $[0, j - 1]$ 范围内有多少个下标 $i$ 满足 $sum[i] < sum[j]$(即有多少个下标可以作为左端点)。
60+
61+
求解比 $x$ 小的数有多少个,可以使用「树状数组」来做。
62+
63+
具体的,我们可以遍历每个位置,假设当前处理到的位置是 $i$,其前缀和值 $t = sum[i]$,我们可以通过查询小于等于 $t - 1$ 的值数量来得知以 $i$ 为右端点的合法子数组数量,统计后,我们需要对数值为 $t$ 出现次数进行加一。
64+
65+
代码:
66+
```Java
67+
class Solution {
68+
int N = 200010, MOD = (int)1e9+7;
69+
int[] tr = new int[N];
70+
int n;
71+
int lowbit(int x) {
72+
return x & -x;
73+
}
74+
void add(int x, int v) {
75+
for (int i = x; i <= 2 * n + 1; i += lowbit(i)) tr[i] += v;
76+
}
77+
int query(int x) {
78+
int ans = 0;
79+
for (int i = x; i > 0; i -= lowbit(i)) ans += tr[i];
80+
return ans;
81+
}
82+
public int subarraysWithMoreZerosThanOnes(int[] nums) {
83+
n = nums.length;
84+
int ans = 0;
85+
int[] sum = new int[n + 10];
86+
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (nums[i - 1] == 0 ? -1 : 1);
87+
for (int i = 0; i <= n; i++) {
88+
int t = sum[i] + n + 1;
89+
ans = (ans + query(t - 1)) % MOD;
90+
add(t, 1);
91+
}
92+
return ans;
93+
}
94+
}
95+
```
96+
* 时间复杂度:预处理前缀和数组的复杂度为 $O(n)$;查询和插入的复杂度均为 $O(\log{n})$,整体复杂度为 $O(n\log{n})$
97+
* 空间复杂度:$O(n)$
98+
99+
---
100+
101+
### 最后
102+
103+
这是我们「刷穿 LeetCode」系列文章的第 `No.2031` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
104+
105+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
106+
107+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
108+
109+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
110+

0 commit comments

Comments
 (0)