Skip to content

Commit 98fac21

Browse files
committed
add challenge day12
1 parent 77fae8b commit 98fac21

File tree

5 files changed

+139
-3
lines changed

5 files changed

+139
-3
lines changed

30-Day LeetCoding Challenge/10.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class MinStack {
6868

6969
上述代码有个问题,`pop` 操作的时间复杂度其实是 O(n),因为需要通过遍历来完成最小值的更新。我们来尝试把它变成 O(1)。
7070

71-
为了直接更新最小值,那么它可能来自于可以直接计算或者提前算好了存起来。由于数据并没有什么数学练习,所以直接计算显然是不现实的,那么我们就从提前算好来考虑。我们可以想象一下在插入一个新数据的时候发生的情况:当前栈中的最小值我是知道的;新数据是否最小我也是知道的。那么就相当于插入数据前的最小值和插入数据后的最小值我都知道。那么反过来,它们其实也就是弹出数据前和弹出数据后的最小值。
71+
为了直接更新最小值,那么它可能来自于可以直接计算或者提前算好了存起来。由于数据并没有什么数学联系,所以直接计算显然是不现实的,那么我们就从提前算好来考虑。我们可以想象一下在插入一个新数据的时候发生的情况:当前栈中的最小值我是知道的;新数据是否最小我也是知道的。那么就相当于插入数据前的最小值和插入数据后的最小值我都知道。那么反过来,它们其实也就是弹出数据前和弹出数据后的最小值。
7272

7373
到此,我们便得到了实现思路。具体代码如下:
7474

30-Day LeetCoding Challenge/11.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ EASY
3232

3333
题目的需求是求一颗二叉树的直径。第一眼看起来似乎有些不知道如何下手,不过我们尝试把这个问题拆解开来。站在一个节点的视角上看来这个目标,那其实只有两种情况:
3434

35-
- 这个最长路径经过"我":那么这个最长路径必然就是以"我"为根节点的子树左侧的高度 + 右侧的高度 + 1。
36-
- 这个最长路径不经过"我":对于当前节点无需进行后续计算
35+
- 这个最长路径以"我"为转折点:那么这个最长路径必然就是以"我"为根节点的子树左侧的高度 + 右侧的高度 + 1。
36+
- 这个最长路径不以"我"为转折点:那么它一定以其他某个节点为转折点,对于"我"无需进行后续计算
3737

3838
通过这种方式,我们成功的把目标拆解为了比较简单的小目标。然后我们只需要找到方法求出子树的高度即可。
3939

30-Day LeetCoding Challenge/12.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const lastStoneWeight = stones => {
2+
stones.sort((a, b) => a - b);
3+
while (stones.length > 1) {
4+
const x = stones.pop();
5+
const y = stones.pop();
6+
if (x === y) continue;
7+
const d = Math.abs(x - y);
8+
let idx = stones.length;
9+
while (idx > 0) {
10+
if (stones[idx - 1] <= d) break;
11+
stones[idx] = stones[idx - 1];
12+
--idx;
13+
}
14+
stones[idx] = d;
15+
}
16+
return stones.length === 1 ? stones[0] : 0;
17+
};
18+
19+
const lastStoneWeight = stones => {
20+
const buckets = new Uint8Array(1001);
21+
let t = 0;
22+
for (const val of stones) ++buckets[val];
23+
for (let i = 1000; i > 0; --i) {
24+
if (!buckets[i]) continue;
25+
if (!t) { buckets[i] % 2 && (t = i); continue; }
26+
const d = Math.abs(t - i);
27+
t = d >= i ? d : (++buckets[d], 0);
28+
--buckets[i++];
29+
}
30+
return t;
31+
};

30-Day LeetCoding Challenge/12.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# 最后一块石头的重量
2+
3+
Hi 大家好,我是张小猪。欢迎来到『宝宝也能看懂』系列特别篇 - 官方小活动 『30-Day LeetCoding Challenge』。
4+
5+
这里是 4 月 12 号的题,也是题目列表中的第 1046 题 -- 『最后一块石头的重量』
6+
7+
## 题目描述
8+
9+
有一堆石头,每块石头的重量都是正整数。
10+
11+
每一回合,从中选出两块 __最重的__ 石头,然后将它们一起粉碎。假设石头的重量分别为 `x` 和 `y`,且 `x` <= `y`。那么粉碎的可能结果如下:
12+
13+
- 如果 `x == y`,那么两块石头都会被完全粉碎;
14+
- 如果 `x != y`,那么重量为 `x` 的石头将会完全粉碎,而重量为 `y` 的石头新重量为 `y-x`
15+
16+
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 `0`
17+
18+
示例:
19+
20+
```shell
21+
输入:[2,7,4,1,8,1]
22+
输出:1
23+
解释:
24+
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
25+
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
26+
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
27+
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。
28+
```
29+
30+
提示:
31+
32+
- `1 <= stones.length <= 30`
33+
- `1 <= stones[i] <= 1000`
34+
35+
## 官方难度
36+
37+
EASY
38+
39+
## 解决思路
40+
41+
看起来描述挺长,但其实就是一个大值依次互相抵消求最后剩余的小游戏。小猪没有想到什么数学方式,所以就正常的根据游戏流程来求最终结果吧。
42+
43+
### 直接方案
44+
45+
由于数字是无序的,所以我们先进行一个排序。然后按照游戏流程,把两个最大值进行抵消,如果存在余量则通过插入排序的方式放入合适的位置。这样直到最后剩余 0 个或者 1 个数字,便得到了结果。
46+
47+
具体代码如下:
48+
49+
```js
50+
const lastStoneWeight = stones => {
51+
stones.sort((a, b) => a - b);
52+
while (stones.length > 1) {
53+
const x = stones.pop();
54+
const y = stones.pop();
55+
if (x === y) continue;
56+
const d = Math.abs(x - y);
57+
let idx = stones.length;
58+
while (idx > 0) {
59+
if (stones[idx - 1] <= d) break;
60+
stones[idx] = stones[idx - 1];
61+
--idx;
62+
}
63+
stones[idx] = d;
64+
}
65+
return stones.length === 1 ? stones[0] : 0;
66+
};
67+
```
68+
69+
### 优化
70+
71+
上面的方案最开始使用了一个全局的排序,随后在遍历中也对余量使用了一次基于遍历的插入行为。那么这里优化方案非常明显,我们可以优化排序的方式,从而简化整个流程。
72+
73+
由于输入数据的范围是 `[1, 1000]`,所以我们可以非常轻松的用桶排序来完成最初的排序,并且后续的余量处理也会变得更加容易。具体代码如下:
74+
75+
```js
76+
const lastStoneWeight = stones => {
77+
const buckets = new Uint8Array(1001);
78+
let t = 0;
79+
for (const val of stones) ++buckets[val];
80+
for (let i = 1000; i > 0; --i) {
81+
if (!buckets[i]) continue;
82+
if (!t) { buckets[i] % 2 && (t = i); continue; }
83+
const d = Math.abs(t - i);
84+
t = d >= i ? d : (++buckets[d], 0);
85+
--buckets[i++];
86+
}
87+
return t;
88+
};
89+
```
90+
91+
## 总结
92+
93+
又是一个基于桶排序完成的优化,在特定的场景下还是『真香』的。希望能帮助到有需要的小伙伴。
94+
95+
如果觉得不错的话,记得『三连』哦。小猪爱你们哟~
96+
97+
## 相关链接
98+
99+
- [活动题目列表](https://github.com/poppinlp/leetcode#30-day-leetcoding-challenge)
100+
- [leetcode 题解 repo](https://github.com/poppinlp/leetcode)
101+
- [我的 segmentfault 专栏](https://segmentfault.com/blog/zxzfbz)
102+
- [我的知乎专栏](https://zhuanlan.zhihu.com/zxzfbz)
103+
104+
![我的微信公众号:张小猪粉鼻子](../resources/qrcode_green.jpeg)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Hi 大家好,我是张小猪。欢迎来到『宝宝也能看懂』系列之 l
3737
- 4 月 9 号:[844. 比较含退格的字符串](./30-Day%20LeetCoding%20Challenge/9.md)
3838
- 4 月 10 号:[155. 最小栈](./30-Day%20LeetCoding%20Challenge/10.md)
3939
- 4 月 11 号:[543. 二叉树的直径](./30-Day%20LeetCoding%20Challenge/11.md)
40+
- 4 月 12 号:[1046. 最后一块石头的重量](./30-Day%20LeetCoding%20Challenge/12.md)
4041

4142
## LEETCODE 周赛
4243

0 commit comments

Comments
 (0)