Skip to content

Commit f0f777a

Browse files
committed
134
1 parent fe47049 commit f0f777a

File tree

3 files changed

+201
-3
lines changed

3 files changed

+201
-3
lines changed

SUMMARY.md

+3-2
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 题到 133](leetcode-101-200.md)
105+
* [101 题到 134](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)
@@ -135,4 +135,5 @@
135135
* [130*. Surrounded Regions](leetcode-130-Surrounded-Regions.md)
136136
* [131. Palindrome Partitioning](leetcode-131-Palindrome-Partitioning.md)
137137
* [132. Palindrome Partitioning II](leetcode-132-Palindrome-PartitioningII.md)
138-
* [133. Clone Graph](leetcode-133-Clone-Graph.md)
138+
* [133. Clone Graph](leetcode-133-Clone-Graph.md)
139+
* [134. Gas Station](leetcode-134-Gas-Station.md)

leetcode-101-200.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,6 @@
6262

6363
<a href="leetcode-132-Palindrome-PartitioningII.html">132. Palindrome Partitioning II</a>
6464

65-
<a href="leetcode-133-Clone-Graph.html">133. Clone Graph</a>
65+
<a href="leetcode-133-Clone-Graph.html">133. Clone Graph</a>
66+
67+
<a href="leetcode-134-Gas-Station.html">134. Gas Station</a>

leetcode-134-Gas-Station.md

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# 题目描述(中等难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/134.png)
4+
5+
把这个题理解成下边的图就可以。
6+
7+
![](https://windliang.oss-cn-beijing.aliyuncs.com/134_2.jpg)
8+
9+
每个节点表示添加的油量,每条边表示消耗的油量。题目的意思就是问我们从哪个节点出发,还可以回到该节点。只能顺时针方向走。
10+
11+
# 解法一 暴力解法
12+
13+
考虑暴力破解,一方面是验证下自己对题目的理解是否正确,另一方面后续的优化也可以从这里入手。
14+
15+
考虑从第 `0` 个点出发,能否回到第 `0` 个点。
16+
17+
考虑从第 `1` 个点出发,能否回到第 1 个点。
18+
19+
考虑从第 `2` 个点出发,能否回到第 `2` 个点。
20+
21+
... ...
22+
23+
考虑从第 `n` 个点出发,能否回到第 `n` 个点。
24+
25+
由于是个圆,得到下一个点的时候我们需要取余数。
26+
27+
```java
28+
public int canCompleteCircuit(int[] gas, int[] cost) {
29+
int n = gas.length;
30+
//考虑从每一个点出发
31+
for (int i = 0; i < n; i++) {
32+
int j = i;
33+
int remain = gas[i];
34+
//当前剩余的油能否到达下一个点
35+
while (remain - cost[j] >= 0) {
36+
//减去花费的加上新的点的补给
37+
remain = remain - cost[j] + gas[(j + 1) % n];
38+
j = (j + 1) % n;
39+
//j 回到了 i
40+
if (j == i) {
41+
return i;
42+
}
43+
}
44+
}
45+
//任何点都不可以
46+
return -1;
47+
}
48+
```
49+
50+
# 解法二 优化尝试一
51+
52+
暴力破解慢的原因就是会进行很多重复的计算。比如下边的情况:
53+
54+
```java
55+
假设当前在考虑 i,先初始化 j = i
56+
* * * * * *
57+
^
58+
i
59+
^
60+
j
61+
62+
随后 j 会进行后移
63+
* * * * * *
64+
^ ^
65+
i j
66+
67+
继续后移
68+
* * * * * *
69+
^ ^
70+
i j
71+
72+
继续后移
73+
* * * * * *
74+
^ ^
75+
j i
76+
77+
此时 j 又回到了第 0 个位置,我们在之前已经考虑过了这个位置。
78+
如果之前考虑第 0 个位置的时候,最远到了第 2 个位置。
79+
那么此时 j 就可以直接跳到第 2 个位置,同时加上当时的剩余汽油,继续考虑
80+
* * * * * *
81+
^ ^
82+
j i
83+
```
84+
85+
利用上边的思想我们可以进行一个优化,就是每考虑一个点,就将当前点能够到达的最远距离记录下来,同时到达最远距离时候的剩余汽油也要记下来。
86+
87+
```java
88+
public int canCompleteCircuit(int[] gas, int[] cost) {
89+
int n = gas.length;
90+
//记录能到的最远距离
91+
int[] farIndex = new int[n];
92+
for (int i = 0; i < farIndex.length; i++) {
93+
farIndex[i] = -1;
94+
}
95+
//记录到达最远距离时候剩余的汽油
96+
int[] farIndexRemain = new int[n];
97+
for (int i = 0; i < n; i++) {
98+
int j = i;
99+
int remain = gas[i];
100+
while (remain - cost[j] >= 0) {
101+
//到达下个点后的剩余
102+
remain = remain - cost[j];
103+
j = (j + 1) % n;
104+
//判断之前有没有考虑过这个点
105+
if (farIndex[j] != -1) {
106+
//加上当时剩余的汽油
107+
remain = remain + farIndexRemain[j];
108+
//j 进行跳跃
109+
j = farIndex[j];
110+
} else {
111+
//加上当前点的补给
112+
remain = remain + gas[j];
113+
}
114+
if (j == i) {
115+
return i;
116+
}
117+
}
118+
//记录当前点最远到达哪里
119+
farIndex[i] = j;
120+
//记录当前点的剩余
121+
farIndexRemain[i] = remain;
122+
}
123+
return -1;
124+
125+
}
126+
```
127+
128+
遗憾的是,这个想法针对 `leetcode` 的测试集速度上没有带来很明显的提升。不过记录已经求出来的解进行优化,这个思想还是经常用的,也就是空间换时间。
129+
130+
让我们换个思路继续优化。
131+
132+
# 解法三 优化尝试二
133+
134+
我们考虑一下下边的情况。
135+
136+
```java
137+
* * * * * *
138+
^ ^
139+
i j
140+
```
141+
142+
当考虑 `i` 能到达的最远的时候,假设是 `j`
143+
144+
那么 `i + 1``j` 之间的节点是不是就都不可能绕一圈了?
145+
146+
假设 `i + 1` 的节点能绕一圈,那么就意味着从 `i + 1` 开始一定能到达 `j + 1`
147+
148+
又因为从 `i` 能到达 `i + 1`,所以从 ` i ` 也能到达 `j + 1`
149+
150+
但事实上,`i` 最远到达 `j` 。产生矛盾,所以 `i + 1` 的节点一定不能绕一圈。同理,其他的也是一样的证明。
151+
152+
所以下一次的 `i` 我们不需要从 `i + 1` 开始考虑,直接从 `j + 1` 开始考虑即可。
153+
154+
还有一种情况,就是因为到达末尾的时候,会回到 `0`
155+
156+
如果对于下边的情况。
157+
158+
```java
159+
* * * * * *
160+
^ ^
161+
j i
162+
```
163+
164+
如果 `i` 最远能够到达 `j` ,根据上边的结论 `i + 1``j` 之间的节点都不可能绕一圈了。想象成一个圆,所以 `i` 后边的节点就都不需要考虑了,直接返回 `-1` 即可。
165+
166+
```java
167+
public int canCompleteCircuit(int[] gas, int[] cost) {
168+
int n = gas.length;
169+
for (int i = 0; i < n; i++) {
170+
int j = i;
171+
int remain = gas[i];
172+
while (remain - cost[j] >= 0) {
173+
//减去花费的加上新的点的补给
174+
remain = remain - cost[j] + gas[(j + 1) % n];
175+
j = (j + 1) % n;
176+
//j 回到了 i
177+
if (j == i) {
178+
return i;
179+
}
180+
}
181+
//最远距离绕到了之前,所以 i 后边的都不可能绕一圈了
182+
if (j < i) {
183+
return -1;
184+
}
185+
//i 直接跳到 j,外层 for 循环执行 i++,相当于从 j + 1 开始考虑
186+
i = j;
187+
188+
}
189+
return -1;
190+
}
191+
```
192+
193+
#
194+
195+
写题的时候先写出暴力的解法,然后再考虑优化,有时候是一种不错的选择。

0 commit comments

Comments
 (0)