Skip to content

Commit e706b30

Browse files
committed
✨feat: Add 380、902
1 parent 9962975 commit e706b30

5 files changed

+244
-1
lines changed

Index/哈希表.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
| [260. 只出现一次的数字 III](https://leetcode-cn.com/problems/single-number-iii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/single-number-iii/solution/gong-shui-san-xie-yi-ti-shuang-jie-ha-xi-zgi4/) | 中等 | 🤩🤩🤩🤩 |
1717
| [268. 丢失的数字](https://leetcode-cn.com/problems/missing-number/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/missing-number/solution/gong-shui-san-xie-yi-ti-wu-jie-pai-xu-ji-te3s/) | 简单 | 🤩🤩🤩🤩 |
1818
| [299. 猜数字游戏](https://leetcode-cn.com/problems/bulls-and-cows/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/bulls-and-cows/solution/gong-shui-san-xie-jian-dan-mo-ni-ti-by-a-tdhs/) | 中等 | 🤩🤩🤩🤩 |
19+
| [380. O(1) 时间插入、删除和获取随机元素](https://leetcode-cn.com/problems/insert-delete-getrandom-o1/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/insert-delete-getrandom-o1/solution/by-ac_oier-tpex/) | 中等 | 🤩🤩🤩🤩🤩 |
1920
| [318. 最大单词长度乘积](https://leetcode-cn.com/problems/maximum-product-of-word-lengths/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/maximum-product-of-word-lengths/solution/gong-shui-san-xie-jian-dan-wei-yun-suan-cqtxq/) | 中等 | 🤩🤩🤩🤩 |
2021
| [432. 全 O(1) 的数据结构](https://leetcode-cn.com/problems/all-oone-data-structure/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/all-oone-data-structure/solution/by-ac_oier-t26d/) | 困难 | 🤩🤩🤩 |
2122
| [447. 回旋镖的数量](https://leetcode-cn.com/problems/number-of-boomerangs/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-boomerangs/solution/gong-shui-san-xie-shu-ju-jie-gou-yun-yon-evu2/) | 中等 | 🤩🤩🤩🤩 |

Index/数位 DP.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
| ------------------------------------------------------------ | ------------------------------------------------------------ | ---- | -------- |
33
| [357. 统计各位数字都不同的数字个数](https://leetcode-cn.com/problems/count-numbers-with-unique-digits/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/count-numbers-with-unique-digits/solution/by-ac_oier-6tfl/) | 中等 | 🤩🤩🤩🤩🤩 |
44
| [600. 不含连续1的非负整数](https://leetcode-cn.com/problems/non-negative-integers-without-consecutive-ones/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/non-negative-integers-without-consecutive-ones/solution/gong-shui-san-xie-jing-dian-shu-wei-dp-y-mh92/) | 困难 | 🤩🤩🤩🤩🤩 |
5+
| [902. 最大为 N 的数字组合](https://leetcode-cn.com/problems/numbers-at-most-n-given-digit-set/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/numbers-at-most-n-given-digit-set/solution/by-ac_oier-8k27/) | 困难 | 🤩🤩🤩🤩🤩 |
56
| [1012. 至少有 1 位重复的数字](https://leetcode-cn.com/problems/numbers-with-repeated-digits/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/numbers-with-repeated-digits/solution/by-ac_oier-2szj/) | 困难 | 🤩🤩🤩🤩🤩 |
67

LeetCode/1011-1020/1012. 至少有 1 位重复的数字(困难).md

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class Solution {
118118
}
119119
```
120120
* 时间复杂度:$O(\log{n})$
121-
* 空间复杂度:$O(1)$
121+
* 空间复杂度:$O(C)$
122122

123123
---
124124

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[380. O(1) 时间插入、删除和获取随机元素](https://leetcode-cn.com/problems/insert-delete-getrandom-o1/solution/by-ac_oier-tpex/)** ,难度为 **中等**
4+
5+
Tag : 「数据结构」、「哈希表」
6+
7+
8+
9+
实现 `RandomizedSet` 类:
10+
11+
* `RandomizedSet()` 初始化 `RandomizedSet` 对象
12+
* `bool insert(int val)` 当元素 `val` 不存在时,向集合中插入该项,并返回 `true`;否则,返回 `false`
13+
* `bool remove(int val)` 当元素 `val` 存在时,从集合中移除该项,并返回 `true`;否则,返回 `false`
14+
* `int getRandom()` 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
15+
16+
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 $O(1)$ 。
17+
18+
示例:
19+
```
20+
输入
21+
["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
22+
[[], [1], [2], [2], [], [1], [2], []]
23+
24+
输出
25+
[null, true, false, true, 2, true, false, 2]
26+
27+
解释
28+
RandomizedSet randomizedSet = new RandomizedSet();
29+
randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。
30+
randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。
31+
randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
32+
randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。
33+
randomizedSet.remove(1); // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
34+
randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。
35+
randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
36+
```
37+
38+
提示:
39+
* $-2^{31} <= val <= 2^{31} - 1$
40+
* 最多调用 `insert``remove``getRandom` 函数 $2 * 10^5$ 次
41+
* 在调用 `getRandom` 方法时,数据结构中 至少存在一个 元素。
42+
43+
---
44+
45+
### 哈希表 + 删除交换
46+
47+
对于 `insert``remove` 操作容易想到使用「哈希表」来实现 $O(1)$ 复杂度,但对于 `getRandom` 操作,比较理想的情况是能够在一个数组内随机下标进行返回。
48+
49+
将两者结合,我们可以将哈希表设计为:以入参 `val` 为键,数组下标 `loc` 为值。
50+
51+
**为了确保严格 $O(1)$,我们不能「使用拒绝采样」和「在数组非结尾位置添加/删除元素」。**
52+
53+
因此我们需要申请一个足够大的数组 `nums`(利用数据范围为 $2* 10^5$),并使用变量 `idx` 记录当前使用到哪一位(即下标在 $[0, idx]$ 范围内均是存活值)。
54+
55+
对于几类操作逻辑:
56+
57+
* `insert` 操作:使用哈希表判断 `val` 是否存在,存在的话返回 `fasle`,否则将其添加到 `nums`,更新 `idx`,同时更新哈希表;
58+
* `remove` 操作:使用哈希表判断 `val` 是否存在,不存在的话返回 `false`,否则从哈希表中将 `val` 删除,同时取出其所在 `nums` 的下标 `loc`,然后将 `nums[idx]` 赋值到 `loc` 位置,并更新 `idx`(含义为将原本处于 `loc` 位置的元素删除),同时更新原本位于 `idx` 位置的数在哈希表中的值为 `loc`(若 `loc``idx` 相等,说明删除的是最后一个元素,这一步可跳过);
59+
* `getRandom` 操作:由于我们人为确保了 $[0, idx]$ 均为存活值,因此直接在 $[0, idx + 1)$ 范围内进行随机即可。
60+
61+
代码:
62+
```Java
63+
class RandomizedSet {
64+
static int[] nums = new int[200010];
65+
Random random = new Random();
66+
Map<Integer, Integer> map = new HashMap<>();
67+
int idx = -1;
68+
public boolean insert(int val) {
69+
if (map.containsKey(val)) return false;
70+
nums[++idx] = val;
71+
map.put(val, idx);
72+
return true;
73+
}
74+
public boolean remove(int val) {
75+
if (!map.containsKey(val)) return false;
76+
int loc = map.remove(val);
77+
if (loc != idx) map.put(nums[idx], loc);
78+
nums[loc] = nums[idx--];
79+
return true;
80+
}
81+
public int getRandom() {
82+
return nums[random.nextInt(idx + 1)];
83+
}
84+
}
85+
```
86+
* 时间复杂度:所有操作均为 $O(1)$
87+
* 空间复杂度:$O(n)$
88+
89+
---
90+
91+
### 最后
92+
93+
这是我们「刷穿 LeetCode」系列文章的第 `No.380` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
94+
95+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
96+
97+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
98+
99+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
100+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[902. 最大为 N 的数字组合](https://leetcode-cn.com/problems/numbers-at-most-n-given-digit-set/solution/by-ac_oier-8k27/)** ,难度为 **困难**
4+
5+
Tag : 「动态规划」、「二分」、「数位 DP」
6+
7+
8+
9+
给定一个按 非递减顺序 排列的数字数组 `digits`。你可以用任意次数 $digits[i]$ 来写的数字。例如,如果 $digits = [1,3,5]$,我们可以写数字,如 `'13'``'551'`, 和 `'1351315'`
10+
11+
返回 **可以生成的小于或等于给定整数** $n$ 的正整数的个数 。
12+
13+
示例 1:
14+
```
15+
输入:digits = ["1","3","5","7"], n = 100
16+
17+
输出:20
18+
19+
解释:
20+
可写出的 20 个数字是:
21+
1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.
22+
```
23+
示例 2:
24+
```
25+
输入:digits = ["1","4","9"], n = 1000000000
26+
27+
输出:29523
28+
29+
解释:
30+
我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,
31+
81 个四位数字,243 个五位数字,729 个六位数字,
32+
2187 个七位数字,6561 个八位数字和 19683 个九位数字。
33+
总共,可以使用D中的数字写出 29523 个整数。
34+
```
35+
示例 3:
36+
```
37+
输入:digits = ["7"], n = 8
38+
39+
输出:1
40+
```
41+
42+
提示:
43+
* $1 <= digits.length <= 9$
44+
* $digits[i].length == 1$
45+
* $digits[i]$ 是从 `'1'` 到 `'9'` 的数
46+
* `digits` 中的所有值都 不同 
47+
* `digits` 按 非递减顺序 排列
48+
* $1 <= n <= 10^9$
49+
50+
---
51+
52+
### 数位 DP + 二分
53+
54+
这是一道「数位 DP」的经典运用题。
55+
56+
由于题目给定的 `digits` 不包含 $0$,因此相当于只需要回答使用 `digits` 的数值能够覆盖 $[1, x]$ 范围内的多少个数字。
57+
58+
起始先将字符串数组 `digits` 转为数字数组 `nums`,假定 `nums` 的长度为 $m$,然后考虑如何求得 $[1, x]$ 范围内合法数字的个数。
59+
60+
假定我们存在函数 `int dp(int x)` 函数,能够返回区间 $[1, x]$ 内合法数的个数,那么配合「容斥原理」我们便能够回答任意区间合法数的查询:
61+
$$
62+
ans_{(l, r)} = dp(r) - dp(l - 1)
63+
$$
64+
对于本题,查询区间的左端点固定为 $1$,同时 $dp(0) = 0$,因此答案为 $dp(x)$。
65+
66+
然后考虑如何实现 `int dp(int x)` 函数,我们将组成 $[1, x]$ 的合法数分成三类:
67+
* 位数和 $x$ 相同,且最高位比 $x$ 最高位要小的,这部分统计为 `res1`
68+
* 位数和 $x$ 相同,且最高位与 $x$ 最高位相同的,这部分统计为 `res2`
69+
* 位数比 $x$ 少,这部分统计为 `res3`
70+
71+
其中 `res1``res3` 求解相对简单,重点落在如何求解 `res2` 上。
72+
73+
**对 $x$ 进行「从高到低」的处理(假定 $x$ 数位为 $n$),对于第 $k$ 位而言($k$ 不为最高位),假设在 $x$ 中第 $k$ 位为 $cur$,那么为了满足「大小限制」关系,我们只能在 $[1, cur - 1]$ 范围内取数,同时为了满足「数字只能取自 `nums`」的限制,因此我们可以利用 `nums` 本身有序,对其进行二分,找到满足 `nums[mid] <= cur` 的最大下标 $r$,根据 $nums[r]$ 与 $cur$ 的关系进行分情况讨论:**
74+
75+
* $nums[r] = cur$: 此时位置 $k$ 共有 $r$ 种选择,而后面的每个位置由于 $nums[i]$ 可以使用多次,每个位置都有 $m$ 种选择,共有 $n - p$ 个位置,因此该分支往后共有 $r * m^{n - p}$ 种合法方案。且由于 $nums[r] = cur$,往后还有分支可决策(需要统计),因此需要继续处理;
76+
* $nums[r] < cur$:此时算上 $nums[r]$,位置 $k$ 共有 $r + 1$ 种选择,而后面的每个位置由于 $nums[i]$ 可以使用多次,每个位置都有 $m$ 种选择,共有 $n - p$ 个位置,因此该分支共有 $(r + 1) * m^{n - p}$ 种合法方案,由于 $nums[r] < cur$,往后的方案数(均满足小于关系)已经在这次被统计完成,累加后进行 `break`
77+
* $nums[r] > cur$:该分支往后不再满足「大小限制」要求,合法方案数为 $0$,直接 `break`
78+
79+
其他细节:实际上,我们可以将 `res1``res2` 两种情况进行合并处理。
80+
81+
代码:
82+
83+
```Java
84+
class Solution {
85+
int[] nums;
86+
int dp(int x) {
87+
List<Integer> list = new ArrayList<>();
88+
while (x != 0) {
89+
list.add(x % 10);
90+
x /= 10;
91+
}
92+
int n = list.size(), m = nums.length, ans = 0;
93+
// 位数和 x 相同
94+
for (int i = n - 1, p = 1; i >= 0; i--, p++) {
95+
int cur = list.get(i);
96+
int l = 0, r = m - 1;
97+
while (l < r) {
98+
int mid = l + r + 1 >> 1;
99+
if (nums[mid] <= cur) l = mid;
100+
else r = mid - 1;
101+
}
102+
if (nums[r] > cur) {
103+
break;
104+
} else if (nums[r] == cur) {
105+
ans += r * (int) Math.pow(m, (n - p));
106+
if (i == 0) ans++;
107+
} else if (nums[r] < cur) {
108+
ans += (r + 1) * (int) Math.pow(m, (n - p));
109+
break;
110+
}
111+
}
112+
// 位数比 x 少的
113+
for (int i = 1, last = 1; i < n; i++) {
114+
int cur = last * m;
115+
ans += cur; last = cur;
116+
}
117+
return ans;
118+
}
119+
public int atMostNGivenDigitSet(String[] digits, int max) {
120+
int n = digits.length;
121+
nums = new int[n];
122+
for (int i = 0; i < n; i++) nums[i] = Integer.parseInt(digits[i]);
123+
return dp(max);
124+
}
125+
}
126+
```
127+
* 时间复杂度:由于 `digits` 最多存在 $9$ 个元素,因此二分的复杂度可以忽略,整体复杂度为 $O(\log{n})$
128+
* 空间复杂度:$O(C)$
129+
130+
---
131+
132+
### 最后
133+
134+
这是我们「刷穿 LeetCode」系列文章的第 `No.902` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
135+
136+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
137+
138+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
139+
140+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
141+

0 commit comments

Comments
 (0)