Skip to content

Commit 82a894d

Browse files
committed
135
1 parent f0f777a commit 82a894d

File tree

3 files changed

+270
-3
lines changed

3 files changed

+270
-3
lines changed

SUMMARY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
* [98. Validate Binary Search Tree](leetCode-98-Validate-Binary-Search-Tree.md)
103103
* [99. Recover Binary Search Tree](leetcode-99-Recover-Binary-Search-Tree.md)
104104
* [100. Same Tree](leetcode-100-Same-Tree.md)
105-
* [101 题到 134](leetcode-101-200.md)
105+
* [101 题到 135](leetcode-101-200.md)
106106
* [101. Symmetric Tree](leetcode-101-Symmetric-Tree.md)
107107
* [102. Binary Tree Level Order Traversal](leetcode-102-Binary-Tree-Level-Order-Traversal.md)
108108
* [103. Binary Tree Zigzag Level Order Traversal](leetcode-103-Binary-Tree-Zigzag-Level-Order-Traversal.md)
@@ -136,4 +136,5 @@
136136
* [131. Palindrome Partitioning](leetcode-131-Palindrome-Partitioning.md)
137137
* [132. Palindrome Partitioning II](leetcode-132-Palindrome-PartitioningII.md)
138138
* [133. Clone Graph](leetcode-133-Clone-Graph.md)
139-
* [134. Gas Station](leetcode-134-Gas-Station.md)
139+
* [134. Gas Station](leetcode-134-Gas-Station.md)
140+
* [135. Candy](leetcode-135-Candy.md)

leetcode-101-200.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,6 @@
6464

6565
<a href="leetcode-133-Clone-Graph.html">133. Clone Graph</a>
6666

67-
<a href="leetcode-134-Gas-Station.html">134. Gas Station</a>
67+
<a href="leetcode-134-Gas-Station.html">134. Gas Station</a>
68+
69+
<a href="leetcode-135-Candy.html">135. Candy</a>

leetcode-135-Candy.md

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# 题目描述(困难难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/135.jpg)
4+
5+
给 N 个小朋友分糖,每个人至少有一颗糖。并且有一个 `rating` 数组,如果小朋友的 `rating`比它旁边小朋友的 `rating` 大(不包括等于),那么他必须要比对应小朋友的糖多。问至少需要分配多少颗糖。
6+
7+
`-` 表示糖,举几个例子。
8+
9+
```java
10+
1 0 2
11+
- - -
12+
- -
13+
总共就需要 5 颗糖。
14+
15+
1 2 2
16+
- - -
17+
-
18+
总共就需要 4 颗糖。
19+
```
20+
21+
# 解法一
22+
23+
根据题目,首先每个小朋友会至少有一个糖。
24+
25+
如果当前小朋友的 `rating` 比后一个小朋友的小,那么后一个小朋友的糖肯定是当前小朋友的糖加 `1`
26+
27+
比如 `ration = [ 5, 6, 7]` ,那么三个小朋友的糖就依次是 `1 2 3 `
28+
29+
如果当前小朋友的 `rating` 比后一个小朋友的大,那么理论上当前小朋友的糖要比后一个的小朋友的糖多,但此时后一个小朋友的糖还没有确定,怎么办呢?
30+
31+
参考 [32题](<https://leetcode.wang/leetCode-32-Longest-Valid-Parentheses.html?q=#%E8%A7%A3%E6%B3%95%E4%BA%94-%E7%A5%9E%E5%A5%87%E8%A7%A3%E6%B3%95>) 的解法五,利用正着遍历,再倒着遍历的思想。
32+
33+
首先我们正着遍历一次,只考虑当前小朋友的 `rating` 比后一个小朋友的小的情况。
34+
35+
接着再倒着遍历依次,继续考虑当前小朋友的 `rating` 比后一个小朋友的小的情况。因为之前已经更新过一次糖果了,此时后一个小朋友的糖如果已经比当前小朋友的糖多了,就不需要进行更新了。
36+
37+
举个例子
38+
39+
```java
40+
初始化每人一个糖
41+
1 2 3 2 1 4
42+
- - - - - -.
43+
44+
只考虑当前小朋友的 rating 比后一个小朋友的小的情况,后一个小朋友的糖是当前小朋友的糖加 1
45+
1 < 2
46+
1 2 3 2 1 4
47+
- - - - - -
48+
-
49+
50+
2 < 3
51+
1 2 3 2 1 4
52+
- - - - - -
53+
- -
54+
-
55+
56+
3 > 2 不考虑
57+
58+
2 > 1 不考虑
59+
60+
1 < 4
61+
1 2 3 2 1 4
62+
- - - - - -
63+
- - -
64+
-
65+
66+
倒过来重新进行
67+
继续考虑当前小朋友的 rating 比后一个小朋友的小的情况。此时后一个小朋友的糖如果已经比当前小朋友的糖多了,就不需要进行更新。
68+
4 1 2 3 2 1
69+
- - - - - -
70+
- - -
71+
-
72+
73+
4 > 1 不考虑
74+
75+
1 < 2
76+
4 1 2 3 2 1
77+
- - - - - -
78+
- - - -
79+
-
80+
81+
2 < 33 的糖果已经比 2 的多了,不需要考虑
82+
83+
3 > 2,不考虑
84+
85+
2 > 1,不考虑
86+
87+
所以最终的糖的数量就是上边的 - 的和。
88+
```
89+
90+
代码的话,我们用一个 `candies` 数组保存当前的分配情况。
91+
92+
```java
93+
public int candy(int[] ratings) {
94+
int n = ratings.length;
95+
int[] candies = new int[n];
96+
//每人发一个糖
97+
for (int i = 0; i < n; i++) {
98+
candies[i] = 1;
99+
}
100+
//正着进行
101+
for (int i = 0; i < n - 1; i++) {
102+
//当前小朋友的 rating 比后一个小朋友的小,后一个小朋友的糖是当前小朋友的糖加 1。
103+
if (ratings[i] < ratings[i + 1]) {
104+
candies[i + 1] = candies[i] + 1;
105+
}
106+
}
107+
//倒着进行
108+
//下标顺序就变成了 i i-1 i-2 i-3 ... 0
109+
//当前就是第 i 个,后一个就是第 i - 1 个
110+
for (int i = n - 1; i > 0; i--) {
111+
//当前小朋友的 rating 比后一个小朋友的小
112+
if (ratings[i] < ratings[i - 1]) {
113+
//后一个小朋友的糖果树没有前一个的多,就更新后一个等于前一个加 1
114+
if (candies[i - 1] <= candies[i]) {
115+
candies[i - 1] = candies[i] + 1;
116+
}
117+
118+
}
119+
}
120+
//计算糖果总和
121+
int sum = 0;
122+
for (int i = 0; i < n; i++) {
123+
sum += candies[i];
124+
}
125+
return sum;
126+
}
127+
```
128+
129+
时间复杂度:O(n)。
130+
131+
空间复杂度:O(n)。
132+
133+
# 解法二
134+
135+
参考 [这里](<https://leetcode.com/problems/candy/discuss/42770/One-pass-constant-space-Java-solution>)
136+
137+
解法一中,考虑到
138+
139+
> 如果当前小朋友的 `rating` 比后一个小朋友的大,那么理论上当前小朋友的糖要比后一个的小朋友的糖多,但此时后一个小朋友的糖还没有确定,怎么办呢?
140+
141+
之前采用了倒着遍历一次的方式进行了解决,这里再考虑另外一种解法。
142+
143+
考虑下边的情况。
144+
145+
![](https://windliang.oss-cn-beijing.aliyuncs.com/135_2.jpg)
146+
147+
对于第 `2``rating 4`,它比后一个 `rating` 要大,所以要取决于再后边的 `rating`,一直走到 `2`,也就是山底,此时对应的糖果数是 `1`,然后往后走,走回山顶,糖果数一次加 `1`,也就是到 `rating 4` 时,糖果数就是 `3` 了。
148+
149+
再一般化,山顶的糖果数就等于从左边的山底或右边的山底依次加 `1`
150+
151+
所以我们的算法只需要记录山顶,然后再记录下坡的高度,下坡的高度刚好是一个等差序列可以直接用公式求和。而山顶的糖果数,取决于左边山底到山顶和右边山底到山顶的哪个高度大。
152+
153+
而产生山底可以有两种情况,一种是 `rating` 产生了增加,如上图。还有一种就是 `rating` 不再降低,而是持平。
154+
155+
知道了上边的想法,基本上就可以写代码了,每个人写出来的应该都不一样,在 `discuss` 区也看到了很多不同的写法,下边说一下我的思路。
156+
157+
抽象出四种情况,这里的高度不是 `rating` 进行相减,而是从山底的 `rating` 到山顶的 `rating` 经过的次数。
158+
159+
1. 左边山底到山顶的高度大,并且右边山底后继续增加。
160+
161+
![](https://windliang.oss-cn-beijing.aliyuncs.com/135_3.jpg)
162+
163+
2. 左边山底到山顶的高度大,并且右边山底是平坡。
164+
165+
![](https://windliang.oss-cn-beijing.aliyuncs.com/135_4.jpg)
166+
167+
3. 右边山底到山顶的高度大,并且右边山底后继续增加。
168+
169+
![](https://windliang.oss-cn-beijing.aliyuncs.com/135_2.jpg)
170+
171+
4. 右边山底到山顶的高度大,并且右边山底是平坡。
172+
173+
![](https://windliang.oss-cn-beijing.aliyuncs.com/135_5.jpg)
174+
175+
176+
177+
有了这四种情况就可以写代码了。
178+
179+
我们用 `total` 变量记录糖果总和, `pre` 变量记录前一个小朋友的糖果数。如果当前的 `rating` 比前一个的 `rating` 大,那么说明在走上坡,可以把前一个小朋友的糖果数加到 `total` 中,并且更新 `pre` 为当前小朋友的糖果数。
180+
181+
如果当前的 `rating` 比前一个的 `rating` 小,说明开始走下坡,用 `down` 变量记录连续多少次下降,此时的 `pre` 记录的就是从左边山底到山底的高度。当出现平坡或上坡的时候,将所有的下坡的糖果数利用等差公式计算。此外根据 `pre``down` 决定山顶的糖果数。
182+
183+
根据当前是上坡还是平坡,来更新 `pre`
184+
185+
大框架就是上边的想法了,还有一些边界需要考虑一下,看一下代码。
186+
187+
```java
188+
public int candy(int[] ratings) {
189+
int n = ratings.length;
190+
int total = 0;
191+
int down = 0;
192+
int pre = 1;
193+
for (int i = 1; i < n; i++) {
194+
//当前是在上坡或者平坡
195+
if (ratings[i] >= ratings[i - 1]) {
196+
//之前出现过了下坡
197+
if (down > 0) {
198+
//山顶的糖果数大于下降的高度,对应情况 1
199+
//将下降的糖果数利用等差公式计算,单独加上山顶
200+
if (pre > down) {
201+
total += count(down);
202+
total += pre;
203+
//山顶的糖果数小于下降的高度,对应情况 3,
204+
//将山顶也按照等差公式直接计算进去累加
205+
} else {
206+
total += count(down + 1);
207+
}
208+
209+
//当前是上坡,对应情况 1 或者 3
210+
//更新 pre 等于 2
211+
if (ratings[i] > ratings[i - 1]) {
212+
pre = 2;
213+
214+
//当前是平坡,对应情况 2 或者 4
215+
//更新 pre 等于 1
216+
} else {
217+
pre = 1;
218+
}
219+
down = 0;
220+
//之前没有出现过下坡
221+
} else {
222+
//将前一个小朋友的糖果数相加
223+
total += pre;
224+
//如果是上坡更新当前糖果数是上一个的加 1
225+
if (ratings[i] > ratings[i - 1]) {
226+
pre = pre + 1;
227+
//如果是平坡,更新当前糖果数为 1
228+
} else {
229+
pre = 1;
230+
}
231+
232+
}
233+
} else {
234+
down++;
235+
}
236+
}
237+
//判断是否有下坡
238+
if (down > 0) {
239+
//和之前的逻辑一样进行相加
240+
if (pre > down) {
241+
total += count(down);
242+
total += pre;
243+
} else {
244+
total += count(down + 1);
245+
}
246+
//将最后一个小朋友的糖果计算
247+
} else {
248+
total += pre;
249+
}
250+
return total;
251+
}
252+
253+
//等差数列求和
254+
private int count(int n) {
255+
return (1 + n) * n / 2;
256+
}
257+
258+
```
259+
260+
这个算法相对于解法一的好处就是将空间复杂度从 `O(n)` 优化到了 `O(1)`
261+
262+
#
263+
264+
解法一虽然空间复杂度大一些,但是很好理解,正着遍历,倒着遍历的思想,每次遇到都印象深刻。解法二主要是对问题进行深入考虑,虽然麻烦些,但空间复杂度确实优化了。

0 commit comments

Comments
 (0)