刻意練習:LeetCode實戰 -- Task29. 加油站

背景

本篇圖文是LSGO軟件技術團隊組織的 第二期基礎算法(Leetcode)刻意練習訓練營 的打卡任務。本期訓練營採用分類別練習的模式,即選擇了五個知識點(數組、鏈表、字符串、樹、貪心算法),每個知識點選擇了 三個簡單、兩個中等、一個困難 等級的題目,共計三十道題,利用三十天的時間完成這組刻意練習。

本次任務的知識點:貪心算法

貪心算法(greedy algorithm),又稱貪婪算法,是一種在每一步選擇中都採取在當前狀態下最好或最優(即最有利)的選擇,從而希望導致結果是最好或最優的算法。

貪心算法在有最優子結構的問題中尤爲有效。最優子結構的意思是局部最優解能決定全局最優解。簡單地說,問題能夠分解成子問題來解決,子問題的最優解能遞推到最終問題的最優解。

貪心算法與動態規劃的不同在於它對每個子問題的解決方案都做出選擇,不能回退。動態規劃則會保存以前的運算結果,並根據以前的結果對當前進行選擇,有回退功能。


題目

  • 題號:134
  • 難度:中等
  • 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 升汽油。
因此,無論怎樣,你都不可能繞環路行駛一週。

實現

第一種:模擬仿真

首先:只有第i站的gas[i] >= cost[i](加入汽油數 >= 消耗汽油數),才能作爲起始加油站。此時剩餘油數爲Remaining = gas[i] - cost[i]

其次:第i+1站的剩餘油數爲Remaining += gas[i+1] - cost[i+1]只有Remaining > 0才能前往下一站,否則不能前往下一站。

以此類推,若能跑完一週,則找到起始的加油站。

  • 執行結果:通過
  • 執行用時:236 ms, 在所有 C# 提交中擊敗了 20.75% 的用戶
  • 內存消耗:24.7 MB, 在所有 C# 提交中擊敗了 14.29% 的用戶
public class Solution
{
    public int CanCompleteCircuit(int[] gas, int[] cost)
    {
        for (int i = 0; i < gas.Length; i++)
        {
            if (gas[i] >= cost[i])
            {
                if (IsCan(gas,cost,i))
                {
                    return i;
                }
            }
        }
        return -1;
    }
    
    private bool IsCan(int[] gas, int[] cost,int index)
    {
        int Remaining = gas[index] - cost[index];
        for (int i = 1; i < gas.Length; i++)
        {
            int j = (index + i) % gas.Length;
            Remaining += gas[j] - cost[j];
            if (Remaining < 0)
            {
                return false;
            }                
        }
        return true;
    }   
}

第二種:連續求和法

易知當總加入汽油數大於等於總消耗油汽油數時,無論如何都有解,關鍵就在於找出起點。

又因爲有解時答案唯一,故可將問題轉化爲求連續和法,若連續和小於等於0,那麼此段中肯定不含可以作起點的油站,將下一個油站記爲起點繼續計算。

  • 執行結果:通過
  • 執行用時:116 ms, 在所有 C# 提交中擊敗了 60.38% 的用戶
  • 內存消耗:24.7 MB, 在所有 C# 提交中擊敗了 14.29% 的用戶
public class Solution
{
    public int CanCompleteCircuit(int[] gas, int[] cost)
    {
        int total = 0;
        int index = 0;
        int keeping = 0;
        for (int i = 0; i < gas.Length; i++)
        {
            int Remaining = gas[i] - cost[i];
            total += Remaining;
            if (keeping > 0)
            {
                keeping += Remaining;
            }
            else
            {
                keeping = Remaining;
                index = i;
            }
        }
        return total < 0 ? -1 : index;
    }
}

python 語言

  • 執行結果:通過
  • 執行用時:40 ms, 在所有 Python3 提交中擊敗了 90.29% 的用戶
  • 內存消耗:14.3 MB, 在所有 Python3 提交中擊敗了 5.52% 的用戶
class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        total, index, keeping = 0, 0, 0
        for i in range(len(gas)):
            remaining = gas[i] - cost[i]
            total += remaining
            if keeping > 0:
                keeping += remaining
            else:
                keeping = remaining
                index = i
        return -1 if total < 0 else index

往期活動

LSGO軟件技術團隊會定期開展提升編程技能的刻意練習活動,希望大家能夠參與進來一起刻意練習,一起學習進步!


我是 終身學習者“老馬”,一個長期踐行“結伴式學習”理念的 中年大叔

我崇尚分享,渴望成長,於2010年創立了“LSGO軟件技術團隊”,並加入了國內著名的開源組織“Datawhale”,也是“Dre@mtech”、“智能機器人研究中心”和“大數據與哲學社會科學實驗室”的一員。

願我們一起學習,一起進步,相互陪伴,共同成長。

後臺回覆「搜搜搜」,隨機獲取電子資源!
歡迎關注,請掃描二維碼:

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