Skip to content

Commit 2d2f6ed

Browse files
committed
✨feat: Add 327、933、729、731、732
1 parent d89a8ac commit 2d2f6ed

7 files changed

+249
-4
lines changed

Index/树状数组.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
| 题目 | 题解 | 难度 | 推荐指数 |
22
| ------------------------------------------------------------ | ------------------------------------------------------------ | ---- | -------- |
33
| [307. 区域和检索 - 数组可修改](https://leetcode-cn.com/problems/range-sum-query-mutable/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/range-sum-query-mutable/solution/guan-yu-ge-lei-qu-jian-he-wen-ti-ru-he-x-41hv/) | 中等 | 🤩🤩🤩🤩🤩 |
4+
| [327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/) | [LeetCode 题解链接](https://leetcode.cn/problems/count-of-range-sum/solution/by-ac_oier-b36o/) | 困难 | 🤩🤩🤩🤩🤩 |
45
| [406. 根据身高重建队列](https://leetcode.cn/problems/queue-reconstruction-by-height/) | [LeetCode 题解链接](https://leetcode.cn/problems/queue-reconstruction-by-height/solution/by-ac_oier-fda2/) | 中等 | 🤩🤩🤩 |
56
| [354. 俄罗斯套娃信封问题](https://leetcode-cn.com/problems/russian-doll-envelopes/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/russian-doll-envelopes/solution/zui-chang-shang-sheng-zi-xu-lie-bian-xin-6s8d/) | 困难 | 🤩🤩🤩 |
67
| [673. 最长递增子序列的个数](https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/solution/gong-shui-san-xie-lis-de-fang-an-shu-wen-obuz/) | 中等 | 🤩🤩🤩🤩 |

Index/线段树.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
| 题目 | 题解 | 难度 | 推荐指数 |
22
| ------------------------------------------------------------ | ------------------------------------------------------------ | ---- | -------- |
33
| [307. 区域和检索 - 数组可修改](https://leetcode-cn.com/problems/range-sum-query-mutable/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/range-sum-query-mutable/solution/by-ac_oier-zmbn/) | 中等 | 🤩🤩🤩🤩🤩 |
4+
| [327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/) | [LeetCode 题解链接](https://leetcode.cn/problems/count-of-range-sum/solution/by-ac_oier-b36o/) | 困难 | 🤩🤩🤩🤩🤩 |
45
| [729. 我的日程安排表 I](https://leetcode-cn.com/problems/my-calendar-i/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-i/solution/by-ac_oier-1znx/) | 中等 | 🤩🤩🤩🤩🤩 |
56
| [731. 我的日程安排表 II](https://leetcode-cn.com/problems/my-calendar-ii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-ii/solution/by-ac_oier-okkc/) | 中等 | 🤩🤩🤩🤩🤩 |
67
| [732. 我的日程安排表 III](https://leetcode-cn.com/problems/my-calendar-iii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-iii/solution/by-ac_oier-cv31/) | 困难 | 🤩🤩🤩🤩🤩 |
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/solution/by-ac_oier-b36o/)** ,难度为 **困难**
4+
5+
Tag : 「前缀和」、「离散化」、「树状数组」、「线段树」、「动态开点」
6+
7+
8+
9+
给你一个整数数组 `nums` 以及两个整数 `lower``upper` 。求数组中,值位于范围 $[lower, upper]$ (包含 `lower` 和 `upper`)之内的 区间和的个数 。
10+
11+
区间和 $S(i, j)$ 表示在 `nums` 中,位置从 $i$ 到 $j$ 的元素之和,包含 $i$ 和 $j$ (`i ≤ j`)。
12+
13+
示例 1:
14+
```
15+
输入:nums = [-2,5,-1], lower = -2, upper = 2
16+
17+
输出:3
18+
19+
解释:存在三个区间:[0,0]、[2,2] 和 [0,2] ,对应的区间和分别是:-2 、-1 、2 。
20+
```
21+
示例 2:
22+
```
23+
输入:nums = [0], lower = 0, upper = 0
24+
25+
输出:1
26+
```
27+
28+
提示:
29+
* $1 <= nums.length <= 10^5$
30+
* $-2^{31} <= nums[i] <= 2^{31} - 1$
31+
* $-10^5 <= lower <= upper <= 10^5$
32+
* 题目数据保证答案是一个 $32$ 位 的整数
33+
34+
---
35+
36+
### 树状数组(离散化)
37+
38+
由于区间和的定义是子数组的元素和,容易想到「前缀和」来快速求解。
39+
40+
对于每个 $nums[i]$ 而言,我们需要统计以每个 $nums[i]$ 为右端点的合法子数组个数(合法子数组是指区间和值范围为 $[lower, upper]$ 的子数组)。
41+
42+
我们可以从前往后处理 $nums$,假设当前我们处理到位置 $k$,同时下标 $[0, k]$ 的前缀和为 $s$,那么以 $nums[k]$ 为右端点的合法子数组个数,等价于在下标 $[0, k - 1]$ 中前缀和范围在 $[s - upper, s - lower]$ 的数的个数。
43+
44+
我们需要使用一个数据结构来维护「遍历过程中的前缀和」,每遍历 $nums[i]$ 需要往数据结构加一个数,同时每次需要查询值在某个范围内的数的个数。涉及的操作包括「单点修改」和「区间查询」,容易想到使用树状数组进行求解。
45+
46+
但值域的范围是巨大的(同时还有负数域),我们可以利用 $nums$ 的长度为 $10^5$ 来做离散化。我们需要考虑用到的数组都有哪些:
47+
48+
1. 首先前缀和数组中的每一位 $s$ 都需要被用到(添加到树状数组中);
49+
2. 同时对于每一位 $nums[i]$(假设对应的前缀和为 $s$),我们都需要查询以其为右端点的合法子数组个数,即查询前缀和范围在 $[s - upper, s - lower]$ 的数的个数。
50+
51+
因此对于前缀和数组中的每一位 $s$,我们用到的数有 $s$、$s - upper$ 和 $s - lower$ 三个数字,共有 $1e5$ 个 $s$,即最多共有 $3 \times 10^5$ 个不同数字被使用,我们可以对所有用到的数组进行排序编号(离散化),从而将值域大小控制在 $3 \times 10^5$ 范围内。
52+
53+
54+
代码:
55+
```Java
56+
class Solution {
57+
int m;
58+
int[] tr = new int[100010 * 3];
59+
int lowbit(int x) {
60+
return x & -x;
61+
}
62+
void add(int x, int v) {
63+
for (int i = x; i <= m; i += lowbit(i)) tr[i] += v;
64+
}
65+
int query(int x) {
66+
int ans = 0;
67+
for (int i = x; i > 0; i -= lowbit(i)) ans += tr[i];
68+
return ans;
69+
}
70+
public int countRangeSum(int[] nums, int lower, int upper) {
71+
Set<Long> set = new HashSet<>();
72+
long s = 0;
73+
set.add(s);
74+
for (int i : nums) {
75+
s += i;
76+
set.add(s);
77+
set.add(s - lower);
78+
set.add(s - upper);
79+
}
80+
List<Long> list = new ArrayList<>(set);
81+
Collections.sort(list);
82+
Map<Long, Integer> map = new HashMap<>();
83+
for (long x : list) map.put(x, ++m);
84+
s = 0;
85+
int ans = 0;
86+
add(map.get(s), 1);
87+
for (int i : nums) {
88+
s += i;
89+
int a = map.get(s - lower), b = map.get(s - upper) - 1;
90+
ans += query(a) - query(b);
91+
add(map.get(s), 1);
92+
}
93+
return ans;
94+
}
95+
}
96+
```
97+
* 时间复杂度:去重离散化的复杂度为 $O(n\log{n})$;统计答案的复杂度为 $O(n\log{n})$
98+
* 空间复杂度:$O(n)$
99+
100+
---
101+
102+
### 线段树(动态开点 + STL)
103+
104+
值域范围爆炸,但是数组长度(查询次数)有限,容易想到「线段树(动态开点)」。
105+
106+
但如果按照我们 [729. 我的日程安排表 I](https://sharingsource.github.io/2022/05/04/729.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20I%EF%BC%88%E4%B8%AD%E7%AD%89%EF%BC%89/)[731. 我的日程安排表 II](https://sharingsource.github.io/2022/05/04/731.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20II%EF%BC%88%E4%B8%AD%E7%AD%89%EF%BC%89/)[732. 我的日程安排表 III](https://sharingsource.github.io/2022/05/04/732.%20%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%20III%EF%BC%88%E5%9B%B0%E9%9A%BE%EF%BC%89/) 系列题解的求解方式「估点 + 静态数组 + 动态 `new`」,仍无法解决 `MLE` 问题。
107+
108+
究其原因,我们在 [日程安排表] 系列中使用到的方式存在「固定双边开点(一旦要对 $u$ 的子节点开点,会同时将左右子节点都开出来)」以及「查询时也会触发开点」的问题,导致我们最多处理值域范围在 $1e9$ 的情况。
109+
110+
对于本题的值域范围远超 $1e9$,我们需要考虑修「静态数组」和「开点方式」来解决 `MLE` 问题:
111+
112+
1. 由于值域太大,采用估点方式来开精挑数组会直接导致 `MLE`,我们使用支持扩容的 `STL` 容器;
113+
2. 由于同时开点和查询也会触发开点,会导致我们不必要的空间浪费,我们直接将开点操作放在 `update` 实现中,并且只有当需要查询到左子节点才对左子节点进行开点,当查询到右子节点才对右子节点进行开点。
114+
115+
代码:
116+
```Java
117+
class Solution {
118+
class Node {
119+
int ls = -1, rs = -1, val = 0;
120+
}
121+
List<Node> tr = new ArrayList<>();
122+
void update(int u, long lc, long rc, long x, int v) {
123+
Node node = tr.get(u);
124+
node.val += v;
125+
if (lc == rc) return ;
126+
long mid = lc + rc >> 1;
127+
if (x <= mid) {
128+
if (node.ls == -1) {
129+
tr.add(new Node());
130+
node.ls = tr.size() - 1;
131+
}
132+
update(node.ls, lc, mid, x, v);
133+
} else {
134+
if (node.rs == -1) {
135+
tr.add(new Node());
136+
node.rs = tr.size() - 1;
137+
}
138+
update(node.rs, mid + 1, rc, x, v);
139+
}
140+
}
141+
int query(int u, long lc, long rc, long l, long r) {
142+
if (u == -1) return 0;
143+
if (r < lc || l > rc) return 0;
144+
Node node = tr.get(u);
145+
if (l <= lc && rc <= r) return node.val;
146+
long mid = lc + rc >> 1;
147+
return query(node.ls, lc, mid, l, r) + query(node.rs, mid + 1, rc, l, r);
148+
}
149+
public int countRangeSum(int[] nums, int lower, int upper) {
150+
long L = 0, R = 0, s = 0;
151+
for (int i : nums) {
152+
s += i;
153+
L = Math.min(Math.min(L, s), Math.min(s - lower, s - upper));
154+
R = Math.max(Math.max(R, s), Math.max(s - lower, s - upper));
155+
}
156+
s = 0;
157+
int ans = 0;
158+
tr.add(new Node());
159+
update(0, L, R, 0, 1);
160+
for (int i : nums) {
161+
s += i;
162+
long a = s - upper, b = s - lower;
163+
ans += query(0, L, R, a, b);
164+
update(0, L, R, s, 1);
165+
}
166+
return ans;
167+
}
168+
}
169+
```
170+
* 时间复杂度:令 $n$ 为数组长度,$m$ 为值域大小,复杂度为 $O(n\log{m})$
171+
* 空间复杂度:$O(n\log{m})$
172+
173+
---
174+
175+
### 线段树(动态指针)
176+
177+
更进一步,我们可以连 `STL` 都不使用,直接在 `Node` 的定义时,将左右子节点的引用存放起来。
178+
179+
虽然这样不会带来空间上的优化,但可以有效避免 `STL` 的创建、访问和扩容(实际运行相比解法二,用时少一半)。
180+
181+
182+
代码:
183+
```Java
184+
class Solution {
185+
class Node {
186+
Node ls, rs;
187+
int val = 0;
188+
}
189+
void update(Node node, long lc, long rc, long x, int v) {
190+
node.val += v;
191+
if (lc == rc) return ;
192+
long mid = lc + rc >> 1;
193+
if (x <= mid) {
194+
if (node.ls == null) node.ls = new Node();
195+
update(node.ls, lc, mid, x, v);
196+
} else {
197+
if (node.rs == null) node.rs = new Node();
198+
update(node.rs, mid + 1, rc, x, v);
199+
}
200+
}
201+
int query(Node node, long lc, long rc, long l, long r) {
202+
if (node == null) return 0;
203+
if (r < lc || l > rc) return 0;
204+
if (l <= lc && rc <= r) return node.val;
205+
long mid = lc + rc >> 1;
206+
return query(node.ls, lc, mid, l, r) + query(node.rs, mid + 1, rc, l, r);
207+
}
208+
public int countRangeSum(int[] nums, int lower, int upper) {
209+
long L = 0, R = 0, s = 0;
210+
for (int i : nums) {
211+
s += i;
212+
L = Math.min(Math.min(L, s), Math.min(s - lower, s - upper));
213+
R = Math.max(Math.max(R, s), Math.max(s - lower, s - upper));
214+
}
215+
s = 0;
216+
int ans = 0;
217+
Node root = new Node();
218+
update(root, L, R, 0, 1);
219+
for (int i : nums) {
220+
s += i;
221+
long a = s - upper, b = s - lower;
222+
ans += query(root, L, R, a, b);
223+
update(root, L, R, s, 1);
224+
}
225+
return ans;
226+
}
227+
}
228+
```
229+
* 时间复杂度:令 $n$ 为数组长度,$m$ 为值域大小,复杂度为 $O(n\log{m})$
230+
* 空间复杂度:$O(n\log{m})$
231+
232+
---
233+
234+
### 最后
235+
236+
这是我们「刷穿 LeetCode」系列文章的第 `No.327` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
237+
238+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
239+
240+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
241+
242+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
243+

LeetCode/721-730/729. 我的日程安排表 I(中等).md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
这是 LeetCode 上的 **[729. 我的日程安排表 I](https://leetcode-cn.com/problems/my-calendar-i/solution/by-ac_oier-1znx/)** ,难度为 **中等**
44

5-
Tag : 「模拟」、「红黑树」、「线段树」
5+
Tag : 「模拟」、「红黑树」、「线段树」、「动态开点」
66

77

88

LeetCode/731-740/731. 我的日程安排表 II(中等).md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
这是 LeetCode 上的 **[731. 我的日程安排表 II](https://leetcode-cn.com/problems/my-calendar-ii/solution/by-ac_oier-okkc/)** ,难度为 **中等**
44

5-
Tag : 「线段树」
5+
Tag : 「线段树」、「动态开点」
66

77

88

LeetCode/731-740/732. 我的日程安排表 III(困难).md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
这是 LeetCode 上的 **[732. 我的日程安排表 III](https://leetcode-cn.com/problems/my-calendar-iii/solution/by-ac_oier-cv31/)** ,难度为 **困难**
44

5-
Tag : 「线段树」
5+
Tag : 「线段树」、「动态开点」
66

77

88

LeetCode/931-940/933. 最近的请求次数(简单).md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
这是 LeetCode 上的 **[933. 最近的请求次数](https://leetcode-cn.com/problems/number-of-recent-calls/solution/by-ac_oier-evqe/)** ,难度为 **简单**
44

5-
Tag : 「线段树」、「分块」
5+
Tag : 「线段树」、「分块」、「动态开点」
66

77

88

0 commit comments

Comments
 (0)