LC134 加油站绕圈圈问题|刷题打卡

225 阅读4分钟

本系列使用IDEA+LEETCODE EDITOR插件,题目描述统一英文题目链接
一、题目描述:

//There are n gas stations along a circular route, where the amount of gas at th
//e ith station is gas[i]. 
//
// You have a car with an unlimited gas tank and it costs cost[i] of gas to trav
//el from the ith station to its next (i + 1)th station. You begin the journey wit
//h an empty tank at one of the gas stations. 
//
// Given two integer arrays gas and cost, return the starting gas station's inde
//x if you can travel around the circuit once in the clockwise direction, otherwis
//e return -1. If there exists a solution, it is guaranteed to be unique 
//
// 
// Example 1: 
//
// 
//Input: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
//Output: 3
//Explanation:
//Start at station 3 (index 3) and fill up with 4 unit of gas. Your tank = 0 + 4
// = 4
//Travel to station 4. Your tank = 4 - 1 + 5 = 8
//Travel to station 0. Your tank = 8 - 2 + 1 = 7
//Travel to station 1. Your tank = 7 - 3 + 2 = 6
//Travel to station 2. Your tank = 6 - 4 + 3 = 5
//Travel to station 3. The cost is 5. Your gas is just enough to travel back to 
//station 3.
//Therefore, return 3 as the starting index.
// 
//
// Example 2: 
//
// 
//Input: gas = [2,3,4], cost = [3,4,3]
//Output: -1
//Explanation:
//You can't start at station 0 or 1, as there is not enough gas to travel to the
// next station.
//Let's start at station 2 and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
//
//Travel to station 0. Your tank = 4 - 3 + 2 = 3
//Travel to station 1. Your tank = 3 - 3 + 3 = 3
//You cannot travel back to station 2, as it requires 4 unit of gas but you only
// have 3.
//Therefore, you can't travel around the circuit once no matter where you start.
//
// 
//
// 
// Constraints: 
//
// 
// gas.length == n 
// cost.length == n 
// 1 <= n <= 104 
// 0 <= gas[i], cost[i] <= 104 

二、思路分析:
首先读题,给你一圈加油站。问什么是一圈而不是一排?因为题目要求你无论从哪个加油站出发都能顺时针走完所有的加油站。然后给了你每站到下个加油站的油耗和能在当前加油站加多少油。让你求从哪个加油站出发能走完所有的。
看到绕圈圈和给的数组参数,就应该想到通过%对下标取余操作实现绕圈圈,而不用到了数组下标最大值下一个又要初始化为0,一个小技巧,可以使代码更优雅。LC有不少绕圈圈问题。
没看到什么难点,先直接来一版暴力解,思路很简单:循环加油站,依次使用当前作为出发点,测试能否走完。但是这种解法能过性能也很差,因为这还是之前提到的线性思维。 如何优化一个非递归的遍历算法,这个才是这期的重点内容

三、AC 代码:
首先展示下暴力版。

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        // 首先gas大于等于cost才能跑完一圈
        int sumGas = 0;
        for (int i : gas) {
            sumGas += i;
        }
        int sumCost = 0;
        for (int i : cost) {
            sumCost += i;
        }
        if(sumGas<sumCost){
            return -1;
        }
        // 遍历一遍火车站即可得出能否跑完全程
        for (int i = 0; i < gas.length; i++) {
            // 包括当前车站
            int visited = 1;
            int tmpIndex = i;
            // 剩余的汽油
            int totalGas = 0;
            // 循环下标用 %gas.length 即可
            while(true){
                totalGas += gas[tmpIndex%gas.length];
                // 能否渠道下一站
                if(totalGas>=cost[tmpIndex%gas.length]){
                    totalGas -= cost[tmpIndex%gas.length];
                    visited ++;
                    tmpIndex ++;
                    if(visited >= gas.length){
                        return i;
                    }
                }else{
                    break;
                }
            }
            //终止条件
            if(visited >= gas.length){
                return i;
            }
        }
        return -1;
    }
}
		解答成功:
			执行耗时:95 ms,击败了18.81% 的Java用户
			内存消耗:39 MB,击败了6.44% 的Java用户

现在开始优化,先落实2个结论:
-- 总油耗>总加油量时无解(在上一个解法中是在加油站的循环外独立判断,可以加入到对加油站的遍历中,优化幅度并不大),
-- 从头遍历加油站,如果之前的结余lastRes + gas[i] - cost[i]<0, i一定不是始发站,因为是顺时针走,当此时只能用下一个加油站作为假设的始发站。

gas: 1 2 3 4 5
cost: 3 5 1 2 4
从index=0站开始,lastRes + gas[0] - cost[0]<0, 0 并不能做出发点,用1;
从index=1站开始,lastRes + gas[1] - cost[1]<0, 1 并不能做出发点,用2;
从index=2站开始,lastRes + gas[2] - cost[2]>=0, 可以作为出发点,往下走
到index=3站,lastRes + gas[3] - cost[3]>=0,往下走
到index=4站,lastRes + gas[3] - cost[3]>=0,可以往下走,此时循环结束。

这时候你要问了,怎么到最后一站就循环结束了呢?不应该继续回到index=0的地方继续判断么? 这就是之前暴力解法性能差的关键点,对于不能作为出发点的index,我们只需要知道它不能作为出发点即可,最后能不能走完,可以靠(总油耗-总加油量)去判断。对这道题,题目已经说了是有唯一解的,意味着一旦一个始发点已经把后面的加油站都走完了,就可以不用从头再判断了,因为它之前的已经被判断过不能作为始发点了。
比如下面7个加油站,当到最后一个加油站也就是0的时候,如果这时发现是要用0作为始发点,此时lastRes一定是0,只要gas[6] - cost[6]>=0,就不需要再去判断前6个点了,只需要依赖整体的总加油量-总油耗是否大于等于0的判断即可。
1 1 1 1 1 1 0

   class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        // 总的剩余油量,小于0时无解
        int sumRes = 0;
        // 要返回的值,从加油站数组下标0开始遍历
        int index = 0;
        // 以某站为起点的过程中,结余的油量
        int tmpRes = 0;
        for (int i = 0; i < gas.length; i++) {
            // 全局看目前的总剩余油量,代替暴力解法中的sumGas-sumCost
            sumRes += gas[i] - cost[i];
            tmpRes += gas[i] - cost[i];
            if(tmpRes < 0){
                // 此时表示从当前加油站已经到不了下一个加油站了
                // 根据题意,只能换下一个加油站为起点
                index = i + 1;
                tmpRes = 0;
            }
        }
        return sumRes < 0? -1: index;
    }
  }
				解答成功:
				执行耗时:0 ms,击败了100.00% 的Java用户
			内存消耗:38.4 MB,击败了95.16% 的Java用户

四、总结:

  1. 非递归的遍历算法,依然会有很多重复计算,要优化这类问题就是要找到这部分。
  2. 了解数组转圈圈可以用%解决。
  3. 局部和整体两手都要抓。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情