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

方法一:暴力

在這裏插入圖片描述
假設我們從第一個加油站出發,我們通過更新每次剩餘的油量,去看能不能返回到原地:
如果可以返回到原地,則說明可以從第一個加油站出發;
如果不可以返回到原地,則從第二個加油站出發,依次遍歷,從第三個,第四個……

 
    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再次遍歷到他們,就不必再重複計算。於是我們有了方法二。
在這裏插入圖片描述

方法二:暴力算法優化一

在這裏插入圖片描述

    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 繼續前移。也就是方法三。
在這裏插入圖片描述

方法三:暴力算法優化二

在這裏插入圖片描述

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中任意一點出發都是不能夠返回原點的。
在這裏插入圖片描述
假設·i+1可以返回原地,那麼它一定能夠到達j+1,又因爲 ·i可以到達i+1,所以i可以到達j+1,這與i最遠到達j是矛盾的。
所以我們不用再像方法三一樣將i往前慢慢移,而是直接令其爲j
在這裏插入圖片描述
有空再寫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章