Skip to content

Commit 5de9924

Browse files
feat: add gas station greedy algorithm
1 parent fb5784f commit 5de9924

File tree

3 files changed

+252
-0
lines changed

3 files changed

+252
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
* [Topological Sort](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/topological_sort.rs)
203203
* [Two Satisfiability](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/two_satisfiability.rs)
204204
* Greedy
205+
* [Gas Station](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/gas_station.rs)
205206
* [Minimum Coin Change](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/minimum_coin_changes.rs)
206207
* [Stable Matching](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/stable_matching.rs)
207208
* [Lib](https://github.com/TheAlgorithms/Rust/blob/master/src/lib.rs)

src/greedy/gas_station.rs

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
//! # Gas Station Problem
2+
//!
3+
//! ## Problem Statement
4+
//!
5+
//! There are n gas stations along a circular route, where the amount of gas
6+
//! at the ith station is `gas[i]`. You have a car with an unlimited gas tank
7+
//! and it costs `cost[i]` of gas to travel from the ith station to its next
8+
//! (i + 1)th station. You begin the journey with an empty tank at one of the
9+
//! gas stations.
10+
//!
11+
//! Given two integer arrays `gas` and `cost`, return the starting gas station's
12+
//! index if you can travel around the circuit once in the clockwise direction;
13+
//! otherwise, return -1. If there exists a solution, it is guaranteed to be unique.
14+
//!
15+
//! ## Algorithm
16+
//!
17+
//! The solution uses a greedy approach:
18+
//! 1. First, check whether the total gas is enough to complete the journey.
19+
//! If the sum of all gas is less than the sum of all costs, return -1.
20+
//! 2. If there is enough gas total, there must be a valid starting index.
21+
//! 3. Greedily calculate the net gain (gas - cost) at each station.
22+
//! 4. If the net gain ever goes below 0 while iterating through the stations,
23+
//! the current starting point is invalid. Start checking from the next station.
24+
//!
25+
//! ## Complexity
26+
//!
27+
//! - Time complexity: O(n) where n is the number of gas stations
28+
//! - Space complexity: O(1)
29+
//!
30+
//! ## References
31+
//!
32+
//! - [LeetCode Problem](https://leetcode.com/problems/gas-station/)
33+
34+
/// Represents a gas station with available gas and cost to travel to the next station
35+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36+
pub struct GasStation {
37+
/// Amount of gas available at this station
38+
pub gas: i32,
39+
/// Cost of gas required to travel to the next station
40+
pub cost: i32,
41+
}
42+
43+
impl GasStation {
44+
/// Creates a new gas station
45+
///
46+
/// # Arguments
47+
///
48+
/// * `gas` - Amount of gas available at this station
49+
/// * `cost` - Cost to travel to the next station
50+
///
51+
/// # Examples
52+
///
53+
/// ```
54+
/// use the_algorithms_rust::greedy::GasStation;
55+
/// let station = GasStation::new(5, 3);
56+
/// assert_eq!(station.gas, 5);
57+
/// assert_eq!(station.cost, 3);
58+
/// ```
59+
pub fn new(gas: i32, cost: i32) -> Self {
60+
Self { gas, cost }
61+
}
62+
63+
/// Returns the net gain (gas - cost) for this station
64+
///
65+
/// # Examples
66+
///
67+
/// ```
68+
/// use the_algorithms_rust::greedy::GasStation;
69+
/// let station = GasStation::new(5, 3);
70+
/// assert_eq!(station.net_gain(), 2);
71+
/// ```
72+
pub fn net_gain(&self) -> i32 {
73+
self.gas - self.cost
74+
}
75+
}
76+
77+
/// Creates a vector of gas stations from parallel arrays of gas quantities and costs
78+
///
79+
/// # Arguments
80+
///
81+
/// * `gas` - Array of gas quantities at each station
82+
/// * `cost` - Array of costs to travel to the next station
83+
///
84+
/// # Panics
85+
///
86+
/// Panics if the lengths of `gas` and `cost` arrays don't match
87+
///
88+
/// # Examples
89+
///
90+
/// ```
91+
/// use the_algorithms_rust::greedy::{create_gas_stations, GasStation};
92+
/// let stations = create_gas_stations(&[1, 2, 3, 4, 5], &[3, 4, 5, 1, 2]);
93+
/// assert_eq!(stations.len(), 5);
94+
/// assert_eq!(stations[0], GasStation::new(1, 3));
95+
/// assert_eq!(stations[4], GasStation::new(5, 2));
96+
/// ```
97+
pub fn create_gas_stations(gas: &[i32], cost: &[i32]) -> Vec<GasStation> {
98+
assert_eq!(
99+
gas.len(),
100+
cost.len(),
101+
"gas and cost arrays must have the same length"
102+
);
103+
gas.iter()
104+
.zip(cost.iter())
105+
.map(|(&g, &c)| GasStation::new(g, c))
106+
.collect()
107+
}
108+
109+
/// Finds the starting gas station index to complete the circular journey
110+
///
111+
/// Returns the index of the gas station from which to start the journey
112+
/// in order to complete a full circuit. Returns -1 if it's impossible to
113+
/// complete the journey.
114+
///
115+
/// # Arguments
116+
///
117+
/// * `stations` - Slice of gas stations along the circular route
118+
///
119+
/// # Returns
120+
///
121+
/// * Index of the starting station (0-indexed) if a solution exists
122+
/// * -1 if no solution exists
123+
///
124+
/// # Examples
125+
///
126+
/// ```
127+
/// use the_algorithms_rust::greedy::{can_complete_journey, create_gas_stations};
128+
/// // Case 1: Solution exists starting at index 3
129+
/// let stations = create_gas_stations(&[1, 2, 3, 4, 5], &[3, 4, 5, 1, 2]);
130+
/// assert_eq!(can_complete_journey(&stations), 3);
131+
///
132+
/// // Case 2: No solution exists
133+
/// let stations = create_gas_stations(&[2, 3, 4], &[3, 4, 3]);
134+
/// assert_eq!(can_complete_journey(&stations), -1);
135+
///
136+
/// // Case 3: Start at index 0
137+
/// let stations = create_gas_stations(&[5, 1, 2, 3, 4], &[4, 4, 1, 5, 1]);
138+
/// assert_eq!(can_complete_journey(&stations), 4);
139+
/// ```
140+
pub fn can_complete_journey(stations: &[GasStation]) -> i32 {
141+
// Calculate total gas and total cost
142+
let total_gas: i32 = stations.iter().map(|s| s.gas).sum();
143+
let total_cost: i32 = stations.iter().map(|s| s.cost).sum();
144+
145+
// If total gas is less than total cost, impossible to complete journey
146+
if total_gas < total_cost {
147+
return -1;
148+
}
149+
150+
// Since we have enough gas, a solution must exist
151+
// Use greedy approach to find the starting station
152+
let mut start = 0;
153+
let mut net = 0;
154+
155+
for (i, station) in stations.iter().enumerate() {
156+
net += station.net_gain();
157+
158+
// If net becomes negative, we can't reach here from current start
159+
// So try starting from the next station
160+
if net < 0 {
161+
start = i + 1;
162+
net = 0;
163+
}
164+
}
165+
166+
start as i32
167+
}
168+
169+
#[cfg(test)]
170+
mod tests {
171+
use super::*;
172+
173+
#[test]
174+
fn test_gas_station_creation() {
175+
let station = GasStation::new(10, 5);
176+
assert_eq!(station.gas, 10);
177+
assert_eq!(station.cost, 5);
178+
assert_eq!(station.net_gain(), 5);
179+
}
180+
181+
#[test]
182+
fn test_create_gas_stations() {
183+
let stations = create_gas_stations(&[1, 2, 3], &[2, 1, 3]);
184+
assert_eq!(stations.len(), 3);
185+
assert_eq!(stations[0], GasStation::new(1, 2));
186+
assert_eq!(stations[1], GasStation::new(2, 1));
187+
assert_eq!(stations[2], GasStation::new(3, 3));
188+
}
189+
190+
#[test]
191+
#[should_panic(expected = "gas and cost arrays must have the same length")]
192+
fn test_create_gas_stations_mismatched_lengths() {
193+
create_gas_stations(&[1, 2], &[1]);
194+
}
195+
196+
#[test]
197+
fn test_can_complete_journey_solution_exists() {
198+
let stations = create_gas_stations(&[1, 2, 3, 4, 5], &[3, 4, 5, 1, 2]);
199+
assert_eq!(can_complete_journey(&stations), 3);
200+
}
201+
202+
#[test]
203+
fn test_can_complete_journey_no_solution() {
204+
let stations = create_gas_stations(&[2, 3, 4], &[3, 4, 3]);
205+
assert_eq!(can_complete_journey(&stations), -1);
206+
}
207+
208+
#[test]
209+
fn test_can_complete_journey_start_at_zero() {
210+
let stations = create_gas_stations(&[3, 1, 1], &[1, 2, 2]);
211+
assert_eq!(can_complete_journey(&stations), 0);
212+
}
213+
214+
#[test]
215+
fn test_can_complete_journey_single_station() {
216+
let stations = create_gas_stations(&[5], &[3]);
217+
assert_eq!(can_complete_journey(&stations), 0);
218+
}
219+
220+
#[test]
221+
fn test_can_complete_journey_single_station_insufficient() {
222+
let stations = create_gas_stations(&[2], &[3]);
223+
assert_eq!(can_complete_journey(&stations), -1);
224+
}
225+
226+
#[test]
227+
fn test_can_complete_journey_two_stations() {
228+
let stations = create_gas_stations(&[1, 2], &[2, 1]);
229+
assert_eq!(can_complete_journey(&stations), 1);
230+
}
231+
232+
#[test]
233+
fn test_can_complete_journey_all_equal() {
234+
let stations = create_gas_stations(&[2, 2, 2, 2], &[2, 2, 2, 2]);
235+
assert_eq!(can_complete_journey(&stations), 0);
236+
}
237+
238+
#[test]
239+
fn test_can_complete_journey_large_numbers() {
240+
let stations = create_gas_stations(&[1000, 500, 300], &[600, 400, 300]);
241+
assert_eq!(can_complete_journey(&stations), 0);
242+
}
243+
244+
#[test]
245+
fn test_can_complete_journey_negative_net_at_start() {
246+
let stations = create_gas_stations(&[1, 5, 3], &[3, 2, 4]);
247+
assert_eq!(can_complete_journey(&stations), 1);
248+
}
249+
}

src/greedy/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
mod gas_station;
12
mod minimum_coin_change;
23
mod stable_matching;
34

5+
pub use self::gas_station::{can_complete_journey, create_gas_stations, GasStation};
46
pub use self::minimum_coin_change::find_minimum_change;
57
pub use self::stable_matching::stable_matching;

0 commit comments

Comments
 (0)