Skip to content

Commit 7768e1f

Browse files
Merge pull request SharingSource#522 from SharingSource/ac_oier
✨feat: Add 327、933、729、731、732、668
2 parents ac8f27d + 69aacc7 commit 7768e1f

8 files changed

+125
-15
lines changed

Index/二分.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
| [528. 按权重随机选择](https://leetcode-cn.com/problems/random-pick-with-weight/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/random-pick-with-weight/solution/gong-shui-san-xie-yi-ti-shuang-jie-qian-8bx50/) | 中等 | 🤩🤩🤩🤩 |
2929
| [540. 有序数组中的单一元素](https://leetcode-cn.com/problems/single-element-in-a-sorted-array/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/single-element-in-a-sorted-array/solution/gong-shui-san-xie-er-duan-xing-fen-xi-yu-17nv/) | 中等 | 🤩🤩🤩🤩 |
3030
| [611. 有效三角形的个数](https://leetcode-cn.com/problems/valid-triangle-number/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/valid-triangle-number/solution/gong-shui-san-xie-yi-ti-san-jie-jian-dan-y1we/) | 中等 | 🤩🤩🤩🤩 |
31+
| [668. 乘法表中第k小的数](https://leetcode.cn/problems/kth-smallest-number-in-multiplication-table/) | [LeetCode 题解链接](https://leetcode.cn/problems/kth-smallest-number-in-multiplication-table/solution/by-ac_oier-7pmt/) | 困难 | 🤩🤩🤩🤩🤩 |
3132
| [704. 二分查找](https://leetcode-cn.com/problems/binary-search/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/binary-search/solution/gong-shui-san-xie-yun-yong-er-fen-zhao-f-5jyj/) | 简单 | 🤩🤩🤩🤩🤩 |
3233
| [728. 自除数](https://leetcode-cn.com/problems/self-dividing-numbers/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/self-dividing-numbers/solution/by-ac_oier-pvb1/) | 简单 | 🤩🤩🤩 |
3334
| [744. 寻找比目标字母大的最小字母](https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/solution/by-ac_oier-to07/) | 简单 | 🤩🤩🤩🤩🤩 |

Index/前缀和.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
| [187. 重复的DNA序列](https://leetcode-cn.com/problems/repeated-dna-sequences/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/repeated-dna-sequences/solution/gong-shui-san-xie-yi-ti-shuang-jie-hua-d-30pg/) | 中等 | 🤩🤩🤩🤩 |
44
| [304. 二维区域和检索 - 矩阵不可变](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/xia-ci-ru-he-zai-30-miao-nei-zuo-chu-lai-ptlo/) | 中等 | 🤩🤩🤩🤩🤩 |
55
| [303. 区域和检索 - 数组不可变](https://leetcode-cn.com/problems/range-sum-query-immutable/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/range-sum-query-immutable/solution/sha-shi-qian-zhui-he-ya-tu-jie-qian-zhui-0rla/) | 简单 | 🤩🤩🤩🤩🤩 |
6+
| [327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/) | [LeetCode 题解链接](https://leetcode.cn/problems/count-of-range-sum/solution/by-ac_oier-b36o/) | 困难 | 🤩🤩🤩 |
67
| [363. 矩形区域不超过 K 的最大数值和](https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/solution/gong-shui-san-xie-you-hua-mei-ju-de-ji-b-dh8s/) | 困难 | 🤩🤩🤩 |
78
| [396. 旋转函数](https://leetcode-cn.com/problems/rotate-function/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/rotate-function/solution/by-ac_oier-sxbi/) | 中等 | 🤩🤩🤩🤩🤩 |
89
| [427. 建立四叉树](https://leetcode.cn/problems/construct-quad-tree/) | [LeetCode 题解链接](https://leetcode.cn/problems/construct-quad-tree/solution/by-ac_oier-maul/) | 中等 | 🤩🤩🤩🤩 |

LeetCode/321-330/327. 区间和的个数(困难).md

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

33
这是 LeetCode 上的 **[327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/solution/by-ac_oier-b36o/)** ,难度为 **困难**
44

5-
Tag : 「前缀和」、「离散化」、「树状数组」、「线段树」、「动态开点」
5+
Tag : 「前缀和」、「离散化」、「树状数组」、「线段树动态开点
66

77

88

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[668. 乘法表中第k小的数](https://leetcode.cn/problems/kth-smallest-number-in-multiplication-table/solution/by-ac_oier-7pmt/)** ,难度为 **困难**
4+
5+
Tag : 「二分」、「计数」
6+
7+
8+
9+
几乎每一个人都用 乘法表,但是你能在乘法表中快速找到第 $k$ 小的数字吗?
10+
11+
给定高度 $m$ 、宽度 $n$ 的一张 $m \times n$ 的乘法表,以及正整数 $k$,你需要返回表中第 $k$ 小的数字。
12+
13+
例 1:
14+
```
15+
输入: m = 3, n = 3, k = 5
16+
17+
输出: 3
18+
19+
解释:
20+
乘法表:
21+
1 2 3
22+
2 4 6
23+
3 6 9
24+
25+
第5小的数字是 3 (1, 2, 2, 3, 3).
26+
```
27+
例 2:
28+
```
29+
输入: m = 2, n = 3, k = 6
30+
31+
输出: 6
32+
33+
解释:
34+
乘法表:
35+
1 2 3
36+
2 4 6
37+
38+
第6小的数字是 6 (1, 2, 2, 3, 4, 6).
39+
```
40+
注意:
41+
* $m$ 和 $n$ 的范围在 $[1, 30000]$ 之间。
42+
* $k$ 的范围在 $[1, m \times n]$ 之间。
43+
44+
---
45+
46+
### 二分 + 计数判定
47+
48+
由于 $n$ 和 $m$ 的数据范围都是 $3 \times 10^4$,总数本身就超过了 $10^7$,我们考虑线性复杂度往下的对数复杂度。
49+
50+
题目要求我们求一维展开有序序列中的第 $k$ 小的数,问题本身具有「二段性」:
51+
52+
* 答案右边的每个数均 **满足**「其在一维展开有序序列中左边数的个数大于等于 $k$ 个」
53+
* 答案左边的每个数均 **不满足**「其在一维展开有序序列中左边数的个数大于等于 $k$ 个」
54+
55+
我们考虑如何进行「二分答案」: 假设当前我们二分到的值是 $mid$,对于乘法表中的每行和每列均是单调递增,我们可以通过累加统计 每行/每列 中比 $mid$ 小的数,记作 $a$,累加统计 每行/每列 中等于 $mid$ 的数,记作 $b$,那么 $cnt = a + b$ 即是整个乘法表中小于等于 $mid$ 的数的个数,再通过 $cnt$ 和 $k$ 的大小关系来指导左右指针的变化。
56+
57+
具体的,假设我们通过枚举行来统计 $a$ 和 $b$,当前枚举到的行号为 $i$(行号从 $1$ 开始),该行的最大数为 $i \times m$:
58+
59+
* 若 $i \times m < mid$,整行都是小于 $mid$ 的数,直接在 $a$ 基础上累加 $m$;
60+
* 若 $i \times m >= mid$,根据 $mid$ 是否存在于该行进行分情况讨论:
61+
* $mid$ 能够被 $i$ 整除,说明 $mid$ 存在于该行,那么比 $mid$ 小的数的个数为 $\frac{mid}{i} - 1$,将其累加到 $a$,同时对 $b$ 进行加一;
62+
* $mid$ 不能够被 $i$ 整除,说明 $mid$ 不存在于该行,那么比 $mid$ 小的数的个数为 $\frac{mid}{i}$,将其累加到 $a$。
63+
64+
> 一些细节:由于乘法表具有对称性,我们统计时可以对 行和列 中较小的一方进行遍历。
65+
66+
代码:
67+
```Java
68+
class Solution {
69+
int n, m, k;
70+
public int findKthNumber(int _m, int _n, int _k) {
71+
n = Math.min(_m, _n); m = Math.max(_m, _n); k = _k;
72+
int l = 1, r = m * n;
73+
while (l < r) {
74+
int mid = l + r >> 1, cnt = getCnt(mid);
75+
if (cnt >= k) r = mid;
76+
else l = mid + 1;
77+
}
78+
return r;
79+
}
80+
int getCnt(int mid) {
81+
int a = 0, b = 0;
82+
for (int i = 1; i <= n; i++) {
83+
if (i * m < mid) {
84+
a += m;
85+
} else {
86+
if (mid % i == 0 && ++b >= 0) a += mid / i - 1;
87+
else a += mid / i;
88+
}
89+
}
90+
return a + b;
91+
}
92+
}
93+
```
94+
* 时间复杂度:$O(\min(n, m) \times \log{nm})$
95+
* 空间复杂度:$O(1)$
96+
97+
---
98+
99+
### 最后
100+
101+
这是我们「刷穿 LeetCode」系列文章的第 `No.668` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
102+
103+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
104+
105+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
106+
107+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
108+

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,26 +85,26 @@ class MyCalendar {
8585
}
8686
}
8787
```
88-
* 时间复杂度:令 $n$ 为 `book` 的最大调用次数,复杂度为 $O(n * \log{n})$
88+
* 时间复杂度:令 $n$ 为 `book` 的最大调用次数,复杂度为 $O(n \times \log{n})$
8989
* 空间复杂度:$O(n)$
9090

9191
---
9292

9393
### 线段树(动态开点)
9494

95-
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 * n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
95+
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 \times n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
9696

9797
如果一道题仅仅是「值域很大」的离线题(提前知晓所有的询问),我们还能通过「离散化」来进行处理,将值域映射到一个小空间去,从而解决 `MLE` 问题。
9898

9999
但对于本题而言,由于「强制在线」的原因,我们无法进行「离散化」,同时值域大小达到 $1e9$ 级别,因此如果我们想要使用「线段树」进行求解,只能采取「动态开点」的方式进行。
100100

101101
动态开点的优势在于,不需要事前构造空树,而是在插入操作 `update` 和查询操作 `query` 时根据访问需要进行「开点」操作。由于我们不保证查询和插入都是连续的,因此对于父节点 $u$ 而言,我们不能通过 `u << 1``u << 1 | 1` 的固定方式进行访问,而要将节点 $tr[u]$ 的左右节点所在 `tr` 数组的下标进行存储,分别记为 `ls``rs` 属性。对于 $tr[u].ls = 0$ 和 $tr[u].rs = 0$ 则是代表子节点尚未被创建,当需要访问到它们,而又尚未创建的时候,则将其进行创建。
102102

103-
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 * n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常数进行分析,才能得到准确的点数上界。
103+
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 \times n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常数进行分析,才能得到准确的点数上界。
104104

105105
动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 $4 * n$ 的空间,因此盲猜 $\log{n}$ 的常数在 $4$ 左右,保守一点可以直接估算到 $6$,因此我们可以估算点数为 $6 * m * \log{n}$,其中 $n = 1e9$ 和 $m = 1e3$ 分别代表值域大小和查询次数。
106106

107-
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 * 10^6$ 以上)。
107+
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 \times 10^6$ 以上)。
108108

109109
代码:
110110
```Java
@@ -152,7 +152,7 @@ class MyCalendar {
152152
}
153153
void pushdown(int u, int len) {
154154
tr[tr[u].ls].add += tr[u].add; tr[tr[u].rs].add += tr[u].add;
155-
tr[tr[u].ls].val += len / 2 * tr[u].add; tr[tr[u].rs].val += len / 2 * tr[u].add;
155+
tr[tr[u].ls].val += (len - len / 2) * tr[u].add; tr[tr[u].rs].val += len / 2 * tr[u].add;
156156
tr[u].add = 0;
157157
}
158158
void pushup(int u) {

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

Lines changed: 4 additions & 4 deletions
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

@@ -50,19 +50,19 @@ MyCalendar.book(25, 55); // returns true
5050
* `add`: 懒标记;
5151
* `max`: 为当前区间的最大值。
5252

53-
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 * n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
53+
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 \times n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
5454

5555
如果一道题仅仅是「值域很大」的离线题(提前知晓所有的询问),我们还能通过「离散化」来进行处理,将值域映射到一个小空间去,从而解决 `MLE` 问题。
5656

5757
但对于本题而言,由于「强制在线」的原因,我们无法进行「离散化」,同时值域大小达到 $1e9$ 级别,因此如果我们想要使用「线段树」进行求解,只能采取「动态开点」的方式进行。
5858

5959
动态开点的优势在于,不需要事前构造空树,而是在插入操作 `update` 和查询操作 `query` 时根据访问需要进行「开点」操作。由于我们不保证查询和插入都是连续的,因此对于父节点 $u$ 而言,我们不能通过 `u << 1``u << 1 | 1` 的固定方式进行访问,而要将节点 $tr[u]$ 的左右节点所在 `tr` 数组的下标进行存储,分别记为 `ls``rs` 属性。对于 $tr[u].ls = 0$ 和 $tr[u].rs = 0$ 则是代表子节点尚未被创建,当需要访问到它们,而又尚未创建的时候,则将其进行创建。
6060

61-
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 * n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常数进行分析,才能得到准确的点数上界。
61+
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 \times n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常数进行分析,才能得到准确的点数上界。
6262

6363
动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 $4 * n$ 的空间,因此盲猜 $\log{n}$ 的常数在 $4$ 左右,保守一点可以直接估算到 $6$,因此我们可以估算点数为 $6 * m * \log{n}$,其中 $n = 1e9$ 和 $m = 1e3$ 分别代表值域大小和查询次数。
6464

65-
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 * 10^6$ 以上)。
65+
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 \times 10^6$ 以上)。
6666

6767
代码:
6868
```Java

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

Lines changed: 4 additions & 4 deletions
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

@@ -50,19 +50,19 @@ myCalendarThree.book(25, 55); // 返回 3
5050
* `add`: 懒标记;
5151
* `max`: 为当前区间的最大值。
5252

53-
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 * n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
53+
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 \times n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
5454

5555
如果一道题仅仅是「值域很大」的离线题(提前知晓所有的询问),我们还能通过「离散化」来进行处理,将值域映射到一个小空间去,从而解决 `MLE` 问题。
5656

5757
但对于本题而言,由于「强制在线」的原因,我们无法进行「离散化」,同时值域大小达到 $1e9$ 级别,因此如果我们想要使用「线段树」进行求解,只能采取「动态开点」的方式进行。
5858

5959
动态开点的优势在于,不需要事前构造空树,而是在插入操作 `update` 和查询操作 `query` 时根据访问需要进行「开点」操作。由于我们不保证查询和插入都是连续的,因此对于父节点 $u$ 而言,我们不能通过 `u << 1``u << 1 | 1` 的固定方式进行访问,而要将节点 $tr[u]$ 的左右节点所在 `tr` 数组的下标进行存储,分别记为 `ls``rs` 属性。对于 $tr[u].ls = 0$ 和 $tr[u].rs = 0$ 则是代表子节点尚未被创建,当需要访问到它们,而又尚未创建的时候,则将其进行创建。
6060

61-
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 * n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常数进行分析,才能得到准确的点数上界。
61+
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 \times n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常数进行分析,才能得到准确的点数上界。
6262

6363
动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 $4 * n$ 的空间,因此盲猜 $\log{n}$ 的常数在 $4$ 左右,保守一点可以直接估算到 $6$,因此我们可以估算点数为 $6 * m * \log{n}$,其中 $n = 1e9$ 和 $m = 1e3$ 分别代表值域大小和查询次数。
6464

65-
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 * 10^6$ 以上)。
65+
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 \times 10^6$ 以上)。
6666

6767
代码:
6868
```Java

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)