Skip to content

Commit b8fa915

Browse files
sozelfistvil02
andauthored
Improve Coin Change Implementation (#773)
* ref: refactor coin change - Use Rust closures to streamline the implementation - Rewrite tests using macro * feat(tests): add some edge tests * tests: add `test_greedy_approach_does_not_work` * tests: add `zero_denominations`-like tests --------- Co-authored-by: vil02 <[email protected]>
1 parent 3c50b91 commit b8fa915

File tree

1 file changed

+78
-54
lines changed

1 file changed

+78
-54
lines changed
Lines changed: 78 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,94 @@
1-
/// Coin change via Dynamic Programming
1+
//! This module provides a solution to the coin change problem using dynamic programming.
2+
//! The `coin_change` function calculates the fewest number of coins required to make up
3+
//! a given amount using a specified set of coin denominations.
4+
//!
5+
//! The implementation leverages dynamic programming to build up solutions for smaller
6+
//! amounts and combines them to solve for larger amounts. It ensures optimal substructure
7+
//! and overlapping subproblems are efficiently utilized to achieve the solution.
28
3-
/// coin_change(coins, amount) returns the fewest number of coins that need to make up that amount.
4-
/// If that amount of money cannot be made up by any combination of the coins, return `None`.
9+
//! # Complexity
10+
//! - Time complexity: O(amount * coins.length)
11+
//! - Space complexity: O(amount)
12+
13+
/// Returns the fewest number of coins needed to make up the given amount using the provided coin denominations.
14+
/// If the amount cannot be made up by any combination of the coins, returns `None`.
15+
///
16+
/// # Arguments
17+
/// * `coins` - A slice of coin denominations.
18+
/// * `amount` - The total amount of money to be made up.
19+
///
20+
/// # Returns
21+
/// * `Option<usize>` - The minimum number of coins required to make up the amount, or `None` if it's not possible.
522
///
6-
/// # Arguments:
7-
/// * `coins` - coins of different denominations
8-
/// * `amount` - a total amount of money be made up.
923
/// # Complexity
10-
/// - time complexity: O(amount * coins.length),
11-
/// - space complexity: O(amount),
24+
/// * Time complexity: O(amount * coins.length)
25+
/// * Space complexity: O(amount)
1226
pub fn coin_change(coins: &[usize], amount: usize) -> Option<usize> {
13-
let mut dp = vec![None; amount + 1];
14-
dp[0] = Some(0);
27+
let mut min_coins = vec![None; amount + 1];
28+
min_coins[0] = Some(0);
1529

16-
// Assume dp[i] is the fewest number of coins making up amount i,
17-
// then for every coin in coins, dp[i] = min(dp[i - coin] + 1).
18-
for i in 0..=amount {
19-
for &coin in coins {
20-
if i >= coin {
21-
dp[i] = match dp[i - coin] {
22-
Some(prev_coins) => match dp[i] {
23-
Some(curr_coins) => Some(curr_coins.min(prev_coins + 1)),
24-
None => Some(prev_coins + 1),
25-
},
26-
None => dp[i],
27-
};
28-
}
29-
}
30-
}
30+
(0..=amount).for_each(|curr_amount| {
31+
coins
32+
.iter()
33+
.filter(|&&coin| curr_amount >= coin)
34+
.for_each(|&coin| {
35+
if let Some(prev_min_coins) = min_coins[curr_amount - coin] {
36+
min_coins[curr_amount] = Some(
37+
min_coins[curr_amount].map_or(prev_min_coins + 1, |curr_min_coins| {
38+
curr_min_coins.min(prev_min_coins + 1)
39+
}),
40+
);
41+
}
42+
});
43+
});
3144

32-
dp[amount]
45+
min_coins[amount]
3346
}
3447

3548
#[cfg(test)]
3649
mod tests {
3750
use super::*;
3851

39-
#[test]
40-
fn basic() {
41-
// 11 = 5 * 2 + 1 * 1
42-
let coins = vec![1, 2, 5];
43-
assert_eq!(Some(3), coin_change(&coins, 11));
44-
45-
// 119 = 11 * 10 + 7 * 1 + 2 * 1
46-
let coins = vec![2, 3, 5, 7, 11];
47-
assert_eq!(Some(12), coin_change(&coins, 119));
48-
}
49-
50-
#[test]
51-
fn coins_empty() {
52-
let coins = vec![];
53-
assert_eq!(None, coin_change(&coins, 1));
54-
}
55-
56-
#[test]
57-
fn amount_zero() {
58-
let coins = vec![1, 2, 3];
59-
assert_eq!(Some(0), coin_change(&coins, 0));
52+
macro_rules! coin_change_tests {
53+
($($name:ident: $test_case:expr,)*) => {
54+
$(
55+
#[test]
56+
fn $name() {
57+
let (coins, amount, expected) = $test_case;
58+
assert_eq!(expected, coin_change(&coins, amount));
59+
}
60+
)*
61+
}
6062
}
6163

62-
#[test]
63-
fn fail_change() {
64-
// 3 can't be change by 2.
65-
let coins = vec![2];
66-
assert_eq!(None, coin_change(&coins, 3));
67-
let coins = vec![10, 20, 50, 100];
68-
assert_eq!(None, coin_change(&coins, 5));
64+
coin_change_tests! {
65+
test_basic_case: (vec![1, 2, 5], 11, Some(3)),
66+
test_multiple_denominations: (vec![2, 3, 5, 7, 11], 119, Some(12)),
67+
test_empty_coins: (vec![], 1, None),
68+
test_zero_amount: (vec![1, 2, 3], 0, Some(0)),
69+
test_no_solution_small_coin: (vec![2], 3, None),
70+
test_no_solution_large_coin: (vec![10, 20, 50, 100], 5, None),
71+
test_single_coin_large_amount: (vec![1], 100, Some(100)),
72+
test_large_amount_multiple_coins: (vec![1, 2, 5], 10000, Some(2000)),
73+
test_no_combination_possible: (vec![3, 7], 5, None),
74+
test_exact_combination: (vec![1, 3, 4], 6, Some(2)),
75+
test_large_denomination_multiple_coins: (vec![10, 50, 100], 1000, Some(10)),
76+
test_small_amount_not_possible: (vec![5, 10], 1, None),
77+
test_non_divisible_amount: (vec![2], 3, None),
78+
test_all_multiples: (vec![1, 2, 4, 8], 15, Some(4)),
79+
test_large_amount_mixed_coins: (vec![1, 5, 10, 25], 999, Some(45)),
80+
test_prime_coins_and_amount: (vec![2, 3, 5, 7], 17, Some(3)),
81+
test_coins_larger_than_amount: (vec![5, 10, 20], 1, None),
82+
test_repeating_denominations: (vec![1, 1, 1, 5], 8, Some(4)),
83+
test_non_standard_denominations: (vec![1, 4, 6, 9], 15, Some(2)),
84+
test_very_large_denominations: (vec![1000, 2000, 5000], 1, None),
85+
test_large_amount_performance: (vec![1, 5, 10, 25, 50, 100, 200, 500], 9999, Some(29)),
86+
test_powers_of_two: (vec![1, 2, 4, 8, 16, 32, 64], 127, Some(7)),
87+
test_fibonacci_sequence: (vec![1, 2, 3, 5, 8, 13, 21, 34], 55, Some(2)),
88+
test_mixed_small_large: (vec![1, 100, 1000, 10000], 11001, Some(3)),
89+
test_impossible_combinations: (vec![2, 4, 6, 8], 7, None),
90+
test_greedy_approach_does_not_work: (vec![1, 12, 20], 24, Some(2)),
91+
test_zero_denominations_no_solution: (vec![0], 1, None),
92+
test_zero_denominations_solution: (vec![0], 0, Some(0)),
6993
}
7094
}

0 commit comments

Comments
 (0)