LeetCode:Gas Station


題目地址:here

There are N gas stations along a circular route, where the amount of gas at station i is gas[i].

You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.

Return the starting gas station's index if you can travel around the circuit once, otherwise return -1.

Note:
The solution is guaranteed to be unique.

題目大意:有n個加油站首尾相連成一個圓,已知每個加油站的油量,以及從第i個加油站到第i+1個加油站需消耗的油量,問:能否開車從某個加油站出發,循環一圈又回到起點,如果可以返回出發的起點(車的郵箱容量是無限的)。

該題實際上可以轉化為求最大字段和問題,只不過是求循環數組的最大子段和。

我們把gas[i] - cost[i]保存在數組edge中,然后對數組edge求最大子段和(求最大子段和和時,我們把edge數組看作首尾相連的循環數組),如果edge的所有元素之和>=0,則汽車可以循環一圈,且這個最大子段的起始位置就是汽車的出發位置;若edge之和小於0,則汽車不能循環一圈。我們之所以對edge數組求最大子段和,是因為按照最大子段和問題的特性,保證了汽車從最大子段的起始端出發到最大子段的末尾的過程中汽車油箱內的油量一直是正數,且汽車走完該子段后,車內可以剩余最多的油。

其實:edge數組所有元素之和>=0 是 汽車可以循環一圈的充分必要條件

另外在求循環數組最大子段和時需要注意幾點:(1)最大子段的起始端只會循環一次,即起始端不會兩次經過起點(2)防止子段出現首尾相連的情況。這兩點代碼里都有注釋

算法時間復雜度O(N),代碼如下:

 1 class Solution {
 2 public:
 3     int canCompleteCircuit(vector<int> &gas, vector<int> &cost)
 4     {
 5         // Note: The Solution object is instantiated only once and is reused by each test case.
 6         const int n = gas.size();
 7         vector<int> edge(n);
 8         int sum = 0;//sum是edge數組所有元素之和
 9         for(int i = 0; i < n; i++)
10         {
11             edge[i] = gas[i] - cost[i];
12             sum += edge[i];
13         }
14         if(sum < 0)
15             return -1;
16         int startpos = 0, endpos = 0;
17         int maxSub = maxSubSegment(edge, startpos, endpos);
18         return startpos;
19     }
20 
21     //求循環數組的最大子段和,並返回最大子段的首尾位置
22     int maxSubSegment(vector<int>&arr, int &startpos, int &endpos)
23     {
24         int n = arr.size();
25         int result = INT_MIN, sum = -1;
26         int starting = -1, ending = -1;//當前子段的起始端
27         int i = 0, j=0; //j是i對n求模之前的值,即i = j%n
28         while(starting < n)
29         {
30             if(i == starting)//防止子段首尾相接
31                 break;
32             if(sum >= 0)
33             {
34                 sum += arr[i];
35                 ending = i;
36             }
37             else
38             {
39                 sum = arr[i];
40                 starting = j;
41                 //設置成j是為了控制while循環結束條件,
42                 //因為子段的起始位置不會過了尾部又循環到數組首部
43                 ending = i;
44             }
45             if(result < sum)
46             {
47                 result = sum;
48                 startpos = starting;
49                 endpos = ending;
50             }
51             i = (i+1)%n;
52             j ++;
53         }
54 
55         return result;
56     }
57 };

 


 

關於循環數組最大子段和可以參考我的另一篇博文:最大子數組和(最大子段和),根據這篇博文,本題有以下代碼:

 1 class Solution {
 2 public:
 3     int canCompleteCircuit(vector<int> &gas, vector<int> &cost)
 4     {
 5         // Note: The Solution object is instantiated only once and is reused by each test case.
 6         const int n = gas.size();
 7         vector<int> edge(n);
 8         int sum = 0;//sum是edge數組所有元素之和
 9         for(int i = 0; i < n; i++)
10         {
11             edge[i] = gas[i] - cost[i];
12             sum += edge[i];
13         }
14         if(sum < 0)
15             return -1;
16         int startpos = 0, endpos = 0;
17         int maxSub = maxSumCycle(edge, startpos, endpos);
18         return startpos;
19     }
20 
21     //求循環數組的最大子段和,並返回最大子段的首尾位置
22     int maxSumCycle(vector<int>&vec, int &left, int&right)
23     {
24         int maxsum = INT_MIN, curMaxSum = 0;
25         int minsum = INT_MAX, curMinSum = 0;
26         int sum = 0;
27         int begin_max = 0, begin_min = 0;
28         int minLeft, minRight;
29         for(int i = 0; i < vec.size(); i++)
30         {
31             sum += vec[i];
32             if(curMaxSum >= 0)
33             {
34                 curMaxSum += vec[i];
35             }
36             else
37             {
38                 curMaxSum = vec[i];
39                 begin_max = i;
40             }
41      
42             if(maxsum < curMaxSum)
43             {
44                 maxsum = curMaxSum;
45                 left = begin_max;
46                 right = i;
47             }
48             ///////////////求和最小的子數組
49             if(curMinSum <= 0)
50             {
51                 curMinSum += vec[i];
52             }
53             else
54             {
55                 curMinSum = vec[i];
56                 begin_min = i;
57             }
58      
59             if(minsum > curMinSum)
60             {
61                 minsum = curMinSum;
62                 minLeft = begin_min;
63                 minRight = i;
64             }
65         }
66         if(maxsum >= sum - minsum)
67             return maxsum;
68         else
69         {
70             left = minRight+1;
71             right = minLeft-1;
72             return sum - minsum;
73         }
74     }
75 };

 


 

網上看到了一種更優雅的實現,它的本質還是最大子段和,代碼如下

 1 public class Solution {
 2     public int canCompleteCircuit(int[] gas, int[] cost) {
 3         // Note: The Solution object is instantiated only once and is reused by each test case.
 4         int N = gas.length, startIndex = -1;
 5         int sum = 0, total = 0;
 6         for(int i = 0; i < N; i++){
 7             sum += (gas[i] - cost[i]);
 8             total += (gas[i] - cost[i]);
 9             if(sum < 0){
10                 startIndex = i; 
11                 sum = 0;
12             }
13         }
14         return total >= 0 ? startIndex + 1 : -1;
15     }
16 }

解釋:

若 i = k時,sum小於0,表示車無法到達第k個加油站,必須從下一個加油站開始出發,total來判斷是否能夠循環一圈

 

【版權聲明】轉載請注明出處:http://www.cnblogs.com/TenosDoIt/p/3389924.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM