Algorithm for truck moving around a circle of gas stations
You have a truck moving around a circular track with gas stations spaced out around the circle. Each station has a finite amount of gas. The gas tank on the truck is infinitely big. The distance between the 开发者_开发百科gas stations requires a certain amount of gas to traverse. You can only move in one direction.
What is the algorithm to use? Which gas station do you start at? Can you get all the way around and back to the start station?
Yes O(n) is possible. Definitely not TSP.
Let xi be the amount of gas available at station i minus the amount of gas required to go to next station.
A requirement is Σ xi ≥ 0 (enough gas to complete a full circle).
Consider Si = x1 + x2 + ... + xi
Note that Sn ≥ 0.
Now pick the smallest (or even largest will do, making it easier to write code for) k such that Sk is the least and start at the station next to it.
Now for k < j ≤ n, we have the gas in tank = Sj - Sk ≥ 0.
for 1 ≤ j ≤ k, we have gas in tank = xk+1 + .. + xn + x1 + x2 + .. + xj = Sn - Sk + Sj ≥ 0.
Thus starting at k+1 will ensure there is enough gas accumulated at each station to get to the next station.
// C++ code. gas[i] is the gas at station i, cost[i] is the cost from station i to (i+1)%n
int circ(vector<int> &gas, vector<int> &cost) {
int min_S=INT_MAX, S=0, position=0;
for(int i=0;i<gas.size();i++)
{
S += gas[i] - cost[i];
if(S<min_S)
{
min_S = S;
position = (i+1) % gas.size();
}
}
if(S>=0)
return position;
else
return -1;
}
Here's an approach that works in O(n)
time and O(1)
space (as opposed to O(n)
space for Aryabhatta's answer).
Start at any station, call it station 0, and advance until you run out of gas. If you don't run out of gas, done. Otherwise, if you run out between stations k and k+1, start over again at station k+1. Make a note if you pass 0 again, and if you run out after that it can't be done.
The reason this works is because if you start at station i and run out of gas between stations k and k+1, then you will also run out of gas before station k+1 if you start at any station between i and k.
Here's an algorithm, given an arrays P (petrol) and D (distance):
int position = 0;
int petrol = P[0];
int distance = D[0];
int start = 0;
while (start < n) {
while (petrol >= distance) {
petrol += P[++position % N] - distance;
distance = D[position % N];
if (position % N == start)
return start;
}
start = position;
petrol = P[start];
}
return -1;
Let cost
be an array of the costs to the next station and gas
be an array of how much fuel we can refill
We calculate the difference between gas[i]
and cost[i]
, called diff
, where i is the current gas station we are at.
If cost[i] > gas[i]
(or diff < 0
), it means we need to have at least cost[i] - gas[i]
fuel when arriving at station i to make it to the next stop, i + 1. If cost[i] <= gas[i]
(diff >= 0
), it is a valid starting point because we can start without gas, fill up, and head to the next station. At worst, we will reach the next stop with an empty tank. At best, we will have extra fuel left when we reach i+1 (diff > 0
)
We actually don’t have to start from one station, traverse n gas stations successfully to find out whether there is a valid tour! As long as summed fuel >= summed cost, there will be a valid tour. So we just need to do an O(n) pass of the arrays
More analysis:
Case1: Tank drops below 0
This will only happen at a stop where diff < 0
. There might be another starting point after that which collects enough excess fuel after traveling one round to pass this station. However, all those stations which we have passed before will not help, so we do not need to consider them (look at case 2 explanation).
Case 2: Tank currently >=0, but we encounter another valid starting point
We can safely ignore this because:
A —— B —— C. If B can reach C, and A can reach B, then A can reach C.
Case 3: Tank currently >=0, not a valid starting point
Continue proceeding to the next one
Case 4: Managed to reach the original starting point!
Yay!
def find_starting_station(gas, cost):
sum_gas = sum_cost = tank = start = 0
for i in range(0, len(gas)):
sum_gas += gas[i]
sum_cost += cost[i]
tank += gas[i] - cost[i]
if tank < 0:
tank = 0
start = i+1
if sum_gas < sum_cost:
return -1
return start
The solution to this problem is based on a greedy algorithm. It is based on the following two observations.
if the total gas > cost, there must be a start index to finish the circle, else there isn't;
as to an index i, if from i, j is the first index that we cannot reach, then any index from i to j, cannot be the start index.
For more explanation, have a look at the following link - Gas Station problem.
This is basically the maximum subarray sum problem. On the other hand, we can look at it from somewhat different POV. Let us find out where will we have the most deficiency in the fuel if we start the journey from the very first gas station. Since we know that reaching this point shall take the most of the fuel, we can conclude that the truck has to start at this point to minimize the negative fuell balance. Below is the solution with the driver program with O(N) time and O(1) space complexity and there is no need for any DP, since everything is done in a single pass and using only two integers to store the start point index and its value (though this is needed only for printing purposes).
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <vector>
#include <algorithm>
using namespace std;
int gasoline[] = {8, 6, 30, 9, 15, 21, 2, 18};
int stations[] = {15, 8, 2, 6, 18, 9, 21, 30};
int rnd_num(const int& low, const int& high)
{
int rndnum = (int) (((double) rand() / (double) RAND_MAX) * (high - low + 1) + low);
return rndnum;
}
void swap(int data[], const int& idxlow, const int& idxhigh)
{
int tmp = data[idxlow];
data[idxlow] = data[idxhigh];
data[idxhigh] = tmp;
}
void print_array(const char* label, int data[], int size)
{
printf("%-10s: ", label);
for (int i = 0; i < size; ++i){
printf("%-3d ", data[i]);
}
printf("\n");
}
void print_vector(const char* label, const vector<int>& data)
{
printf("%-10s: ", label);
for (vector<int>::size_type i = 0; i < data.size(); ++i){
printf("%-3d ", data[i]);
}
printf("\n");
}
void shuffle(int data[], int size)
{
for (int i = 0; i < size - 1; ++i){
int idx = rnd_num(i + 1, size - 1);
swap(data, i, idx);
}
}
void run(int gas[], int dist[], int size)
{
vector<int> path;
int diff = 0, vidx, minidx = 0, minval = gas[0] - dist[0];
path.resize(size);
for (int i = 0; i < size; ++i) {
diff += gas[i] - dist[i];
if (i == size - 1){
vidx = 0; //path[0] = diff;
}
else {
vidx = i + 1; //path[i + 1] = diff;
}
path[vidx] = diff;
if (diff < minval) {
minval = diff;
minidx = vidx;
}
}
print_vector("PATHS ", path);
printf("MINIDX: %d\nMINVAL: %d\n", minidx, minval);
}
int main()
{
int size = sizeof(stations)/sizeof(stations[0]);
srand((unsigned)time(NULL));
shuffle(gasoline, sizeof(gasoline)/sizeof(gasoline[0]));
shuffle(stations, sizeof(stations)/sizeof(stations[0]));
print_array("GASOLINE ", gasoline, sizeof(gasoline)/sizeof(gasoline[0]));
print_array("STATIONS ", stations, sizeof(stations)/sizeof(stations[0]));
run(gasoline, stations, size);
return 0;
}
starting from any station, try to move to the next one (to the right). If you can't (run out of gas), we have to take gas from the left of the starting point (move starting station to the left).
start = 0
end = start
amount = 0
for i in range(n):
if amount > 0:
amount += (gas[end] - cost[end])
end = (end + 1) % n
else:
start = (start - 1 + n) % n
amount += (gas[start] - cost[start])
if amount >= 0: return start
return -1
`
Here is my solution in python. Addition to other explained once we keep a variable to calculate the need for the previous stations and when we are at the end of the array we just check if our leftover is above that need and return accordingly.
if not gas or not cost:
return - 1
index = 0
start = 0
total = 0
need = 0
while index < len(gas):
# If we can travel without any leftover.
# What is our status since start, if total is
# below zero that means we are in a worse situation
# then we were.
total += gas[index] - cost[index]
if total < 0 :
need -= total
start = index + 1
total = 0
index += 1
if total - need >= 0:
return start
else:
return -1
We are given with 2 arrays, one for amount of gas available at bunk Bi and one for the amount of gas we spend to reach from i to i+1. We try to start from the first bunker and assume it to be the solution. When even we see that residue gas + available gas at bunker is < amount of gas required we reset the solution to the next index repeat the process till we reach the starting point(our last assumed solution)
public int getStationIndex() {
int answer = -1;
if (gasAvailableArray.length != gasExpenditureArray.length) {
throw new IllegalArgumentException("Invalid input array provided");
}
Queue<Integer> queue = new ArrayDeque<>();
int residue = 0;
for (int index = 0; ; ) {
if (index >= gasAvailableArray.length) {
index = index % (gasAvailableArray.length - 1);
}
if (index == answer) {
return answer;
}
if (residue + gasAvailableArray[index] - gasExpenditureArray[index] >= 0) {
// Only set a new answer when we had a reset in last iteration
if (answer == -1) {
answer = index;
}
residue += gasAvailableArray[index] - gasExpenditureArray[index];
queue.add(index);
} else {
while (!queue.isEmpty()) {
queue.poll();
}
}
answer = -1;
index++;
}
}
static int findIndex(int A[], int B[]) {
int N = A.length;
int start=0;
boolean isBreak=false;
int total = 0;
do {
for(int i=start;i<start+A.length;i++) {
int c = A[i%N] - B[i%N];
total +=c;
if(total < 0) {
total= 0;
isBreak=true;
break;
}
}
if(isBreak) {
start++;
isBreak=false;
} else {
return start;
}
} while(start < A.length);
return -1;
}
static int find(int A[], int B[]) {
for (int start = 0; start < A.length; start++) {
int x = findIndex(A, B, start);
if (x != -1)
return x;
}
return -1;
}
static int findIndex(int A[], int B[], int start) {
int total=0;
int N = A.length;
for (int i = start; i < start + A.length; i++) {
int c = A[i % N] - B[i % N];
total += c;
if (total < 0) {
total = 0;
return -1;
}
}
return start;
}
Test cases:
- Test case
A : { 1, 2, 3, 4, 5 }, B : { 1, 3, 2, 4, 5 }
--> index2
- Test case
A : {2,2,1}, B : {2,1,2}
index -->0
- Test case
A : {1,2}, B {2,1}
--> index1
- Test case
A : {1,2,1}, B {2,1,3}
--> index-1
static int getIndex(int A[], int B[]) {
int start = -1;
int N = A.length;
for (int i = 0; i < N; i++) {
int c = A[i] - B[i];
if (c >= 0) {
int j = i + 1;
while (c >= 0 && j < i + N) {
c += A[j % N] - B[j % N];
j++;
}
if (c >= 0) {
start = i;
break;
}
}
}
return start;
}
精彩评论