Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e706b30

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

File tree

5 files changed

+244
-1
lines changed

5 files changed

+244
-1
lines changed

‎Index/哈希表.md‎

Lines changed: 1 addition & 0 deletions
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‎

Lines changed: 1 addition & 0 deletions
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‎

Lines changed: 1 addition & 1 deletion
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

Lines changed: 100 additions & 0 deletions
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+
Lines changed: 141 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /