LeetCode刻意练习29--加油站

题目:
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

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

输出: 3

理解题目:

3
4
5
1
2
1
2
3
4
5
方法一:暴力

LeetCode刻意练习29--加油站_第1张图片
假设我们从第一个加油站出发,我们通过更新每次剩余的油量,去看能不能返回到原地:
如果可以返回到原地,则说明可以从第一个加油站出发;
如果不可以返回到原地,则从第二个加油站出发,依次遍历,从第三个,第四个……

 
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int j;
        int rest;
        for (int i = 0; i < gas.length; i++) {
            rest = gas[i];
            j = i;
            while (rest >= cost[j]) {
                rest += gas[(j + 1) % gas.length] - cost[j];
                j = (j + 1) % gas.length;
                if (j == i)
                    return i;
            }
        }
        return -1;
    }

通过上述暴力算法我们可以发现,当我们每次从第i个加油站出发, 在用j跟踪它最远能到达的地方时,j要遍历所有不是i的加油站,直到剩余汽油少于0。假设我们从第一和第二个加油站出发,已经被证明不能够回到原点,那么当我们从第3个加油站出发时,如果能够回到原点,那么就意味着j跟踪到了第1个加油站和第2个加油站。而第一和第二个加油站我们在从他们出发时就已经遍历过了,这样我们可以将他们记忆起来,当j再次遍历到他们,就不必再重复计算。于是我们有了方法二。
LeetCode刻意练习29--加油站_第2张图片

方法二:暴力算法优化一

LeetCode刻意练习29--加油站_第3张图片

    public int canCompleteCircuit1(int[] gas, int[] cost) {
        int[] far = new int[gas.length];
        for (int i = 0; i < gas.length; i++) {
            far[i] = -1;
        }
        int[] far_rest = new int[gas.length];
        int j;
        int rest;
        for (int i = 0; i < gas.length; i++) {
            rest = gas[i];
            j = i;
            while (rest >= cost[j]) {
                rest += gas[(j + 1) % gas.length] - cost[j];
                j = (j + 1) % gas.length;
                if (far[j] != -1) {
                    rest += far_rest[j];
                    if (rest >= 0)
                        j = far[j];
                }
                if (j == i)
                    return i;
            }
        }
        return -1;
    }

方法二主要优化的是一次遍历中的j
我们再进行优化,这次我们优化的结束一次遍历进行再次遍历时的情况,在暴力算法中,当结束一次遍历再次进行一次遍历时,我们遍历的是下一个加油站,我们令j=i,重新遍历。

现在我们换个想法,j保持在原处不变,由于从出发的加油站并不能返回原地,因此我们把出发的加油站删去。然后我们看剩余量是不是少于0,如果少于零则 i 继续前移。也就是方法三。
LeetCode刻意练习29--加油站_第4张图片

方法三:暴力算法优化二

LeetCode刻意练习29--加油站_第5张图片

public int canCompleteCircuit2(int[] gas, int[] cost) {
        int[] diff = new int[gas.length];
        for (int i = 0; i < gas.length; i++)
            diff[i] = gas[i] - cost[i];
   
        if (gas.length == 1 && diff[0] >= 0)
            return 0;

        int start = 0, end = 1;
        int rest = diff[start];
       while (end != start || rest < 0) {

            while (rest < 0 && start != end) {
                rest = rest - diff[start];
                start = (start + 1) % gas.length;
                if (start == 0)
                    return -1;
            }
            rest = rest + diff[end];
            end = (end + 1) % gas.length;
        }
        return start;
    }

好像还能继续优化,
如果i最远只能到达j,那么从 i+1j-1中任意一点出发都是不能够返回原点的。
LeetCode刻意练习29--加油站_第6张图片
假设·i+1可以返回原地,那么它一定能够到达j+1,又因为 ·i可以到达i+1,所以i可以到达j+1,这与i最远到达j是矛盾的。
所以我们不用再像方法三一样将i往前慢慢移,而是直接令其为j
LeetCode刻意练习29--加油站_第7张图片
有空再写。

你可能感兴趣的:(#,LeetCode刻意练习)