LeetCode134 算法分析

LeetCode134

題目簡述

判斷數組中是否存在合法路徑
難度:中等
描述:
在一條環路上有 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 升汽油。
因此,無論怎樣,你都不可能繞環路行駛一週。

題解

思路

這道題嘛,最簡單的想法就是 O(n^2) 的算法
嵌套循環,往復計算檢驗,直到找出符合要求的點

當然 O(n^2) 的算法往往都有優化的空間
在優化之前,我們考慮一下兩個要素:

  1. 如果sum(gas[i] - cost[i]) < 0 (0 <= i < gas.size()),那麼必然不存在合法路徑
  2. gas - cost小於零的節點必然不能作爲起點

以上明確之後,其實這道題就有那麼一點最大子列和的味道了:

  1. 創建新數組 total ,使taotal[i] = gas[i] - cost[i]
  2. 遍歷 total ,找出不爲零的節點標記
  3. 從標記的不爲零的節點開始用 sum 累加,如果 sum < 0 則重置
  4. 當遍歷節點數 count == gas.size()sum > 0 時,返回標記的節點

由此思路便可以着手代碼實現了

最初實現代碼

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        if(gas.empty()) return -1;
        int len = gas.size();
        vector<int> total(len);
        for(int i = 0; i < len; i++)
            total[i] = gas[i] - cost[i];
        int sum = 0, count = 0, tag = 0, x = 0;
        bool flag = false;
        for(auto i : total) {
            if(i >= 0) x++;
            sum += i;
        }
        if(sum < 0) return -1;
        if(gas.size() == 1) return 0;
        sum = 0;
        for(int i = 0; i < len; i++) {
            if(total[i] >= 0 && !flag) {
                x--;
                if(x < 0) break;
                flag = true;
                tag = i;
            }
            if(flag) {
                sum += total[i];
                count++;
                if(sum < 0) {
                    sum = 0;
                    flag = false;
                    count = 0;
                }
            }
            if(i == len - 1) i = -1;
            if(count == len - 1) {
                if(sum < 0) {
                    sum = 0;
                    flag = false;
                    count = 0;
                }
                else return tag;
            }
        }
        return -1;
    }
};

其實可以發現,雖然基本遵循了我構想的思路
但代碼實現上存在很多冗餘
有些變量定義的也不合時宜
雖然通過了測試,卻只能說是勉勉強強

優化實現代碼

抱着學習的態度翻看了官方題解和一些大佬思路
我倒是很驚喜的發現自己的思路和官方給的題解幾乎吻合
但是官方給的代碼可讀性就比我的強太多了。。。
同時在代碼中有一些小的改進
免去了直接 “翻譯思路” 從而產生的一些硬直
比如省去了開闢 total 的空間
同時也免去了單獨循環遍歷求一次 sum 的開銷等:

class Solution {
  public:
  int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
    int n = gas.size();

    int total_tank = 0;
    int curr_tank = 0;
    int starting_station = 0;
    for (int i = 0; i < n; ++i) {
      total_tank += gas[i] - cost[i];
      curr_tank += gas[i] - cost[i];
      if (curr_tank < 0) {
        starting_station = i + 1;
        curr_tank = 0;
      }
    }
    return total_tank >= 0 ? starting_station : -1;
  }
};

只能說即使擁有正確的思路,真正的好算法也是需要打磨的啊
繼續努力吧

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