408算法练习——加油站(数组)

加油站

问题链接:https://leetcode-cn.com/problems/gas-station/

一、问题描述  

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

说明: 

如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。


示例 1:

输入:
gas = [1,2,3,4,5]
cost = [3,4,5,1,2]

输出: 3

解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。


示例 2:

输入:
gas = [2,3,4]
cost = [3,4,3]

输出: -1

解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

 

二、问题分析

  最直观的方法就是把所有起点都算一遍。这么做当然可以,但是会增加时间复杂度。如果计算所有起点出发的循环,就很容易发现在多次循环中包含大量重复计算。所以可以把计算结果保存下来,后续如果需要的时候直接取用。可以把这种方法叫做记账法(备忘录法)

  对于该问题,因为每走一步都要计算汽油的消耗和添加,那么就创建一个数组,保存从当前位置出发到达下一位置时油箱里的油量。当然,这里就需要一个假设条件了。假如汽车可以向未来贷油,也就是说,汽车没油也能跑。数组中就可以保存负值,负值的含义就是欠下的油。

  408算法练习——加油站(数组)

 

   不难发现,如果debt数组中所有的值相加为负就说明不管从哪里开始走,汽车的油总是不够用的。

  接下来考虑和不为0的情况,因为从表面看上去如果数组和大于等于0,是有可能环游一周,但是如果中间出现连续的负数呢?是否有可能使车停在某个位置加不上油?这需要经过证明才能确定。

  简单证明:假设debt在某个位置值为正,把他记作d_i,如果在d_i后出现连续的负数,使得d_i加上这些负值的结果为负,那么从i位置出发就无法环游一周。如果数组中有多对这样的案例,那么数组加和最终值一定是负值,否则汽车总能找到一个出发点完成环游。这个证明不是严谨的数学证明,但是是一个推理过程。如果代码通过了所有测试,那么很有可能说明该方法可行。

 

三、算法及代码

  

 1 class Solution {
 2     public int canCompleteCircuit(int[] gas, int[] cost) {
 3         int[] debt = new int[gas.length]; 
 4         int count = 0;
 5         for(int i=0;i<gas.length;i++){//构建debt数组并求和
 6             debt[i] = gas[i]-cost[i];
 7             count = count + debt[i];
 8         }
 9         if(count<0){
10             return -1;
11         }
12         /*
13         接下来找起始点,找起始点的想法和上面证明可行性用的类似,
14         如果从一个正数出发在与后面的数字求和的过程中出现负数,那么就说明不能从该位置出发
15          */
16         for(int i=0;i<debt.length;i++){
17             int x = 0;
18             for(int j=i;j<debt.length;j++){//两个for循环实现循环遍历的功能
19                 if(x < 0){
20                     break;
21                 }else{
22                     x = x+debt[j];
23                 }
24             }
25             for(int k=0;k<i;k++){
26                 if(x < 0){
27                     break;
28                 }else{
29                     x = x+debt[k];
30                 }
31             }
32             if(x >= 0){
33                 return i;
34             }
35         }
36         return 0; 
37     }
38 }

测试通过,说明具有可行性

408算法练习——加油站(数组)

 

上一篇:408每日算法——连续子数组长度(2016年清华912)


下一篇:408每日算法——反转整数