788-coin-change-dp — Coin Change
Tutorial Video
Text description (accessibility)
This video demonstrates the "788-coin-change-dp — Coin Change" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. The coin change problem asks: given denominations of coins and an amount, what is the minimum number of coins needed to make that amount? Key difference from OCaml: 1. **Sentinel value**: Rust uses `usize::MAX` as "impossible"; OCaml uses `max_int` — same concept, different constant name.
Tutorial
The Problem
The coin change problem asks: given denominations of coins and an amount, what is the minimum number of coins needed to make that amount? Greedy fails for non-standard denominations (e.g., coins of 1, 3, 4 — greedy gives 3 coins for 6, but optimal is 2). DP finds the true minimum. The counting variant (number of ways to make change) is used in combinatorics and financial applications. Canonical in algorithm courses since the 1970s.
🎯 Learning Outcomes
coin_change(coins, amount) -> Option<usize> returning None when impossibledp[i] = min over coins c: dp[i-c] + 1coin_change_ways)Code Example
#![allow(clippy::all)]
//! # Coin Change
pub fn coin_change(coins: &[usize], amount: usize) -> Option<usize> {
let mut dp = vec![usize::MAX; amount + 1];
dp[0] = 0;
for i in 1..=amount {
for &coin in coins {
if coin <= i && dp[i - coin] != usize::MAX {
dp[i] = dp[i].min(dp[i - coin] + 1);
}
}
}
if dp[amount] == usize::MAX {
None
} else {
Some(dp[amount])
}
}
pub fn coin_change_ways(coins: &[usize], amount: usize) -> usize {
let mut dp = vec![0usize; amount + 1];
dp[0] = 1;
for &coin in coins {
for i in coin..=amount {
dp[i] += dp[i - coin];
}
}
dp[amount]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_change() {
assert_eq!(coin_change(&[1, 2, 5], 11), Some(3));
}
#[test]
fn test_ways() {
assert_eq!(coin_change_ways(&[1, 2, 5], 5), 4);
}
#[test]
fn test_impossible() {
assert_eq!(coin_change(&[2], 3), None);
}
}Key Differences
usize::MAX as "impossible"; OCaml uses max_int — same concept, different constant name.usize::MAX + 1 would panic; the != usize::MAX guard prevents this; OCaml has the same issue with max_int + 1.Option<usize> clearly communicates impossibility; OCaml might return -1 or Int.max_int without a dedicated option type.coin_change_ways uses a different loop structure (iterate coins in outer loop for combinations); both languages implement it identically.OCaml Approach
OCaml implements the same bottom-up DP with Array.make (amount+1) max_int and a nested for loop. Functional style uses List.fold_left over coins. The Int.max_int sentinel instead of usize::MAX. Reconstruction uses a prev: int array tracking which coin was used at each step. OCaml's min_coins function in competitive programming is often a one-liner using Array.fold_left.
Full Source
#![allow(clippy::all)]
//! # Coin Change
pub fn coin_change(coins: &[usize], amount: usize) -> Option<usize> {
let mut dp = vec![usize::MAX; amount + 1];
dp[0] = 0;
for i in 1..=amount {
for &coin in coins {
if coin <= i && dp[i - coin] != usize::MAX {
dp[i] = dp[i].min(dp[i - coin] + 1);
}
}
}
if dp[amount] == usize::MAX {
None
} else {
Some(dp[amount])
}
}
pub fn coin_change_ways(coins: &[usize], amount: usize) -> usize {
let mut dp = vec![0usize; amount + 1];
dp[0] = 1;
for &coin in coins {
for i in coin..=amount {
dp[i] += dp[i - coin];
}
}
dp[amount]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_change() {
assert_eq!(coin_change(&[1, 2, 5], 11), Some(3));
}
#[test]
fn test_ways() {
assert_eq!(coin_change_ways(&[1, 2, 5], 5), 4);
}
#[test]
fn test_impossible() {
assert_eq!(coin_change(&[2], 3), None);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_change() {
assert_eq!(coin_change(&[1, 2, 5], 11), Some(3));
}
#[test]
fn test_ways() {
assert_eq!(coin_change_ways(&[1, 2, 5], 5), 4);
}
#[test]
fn test_impossible() {
assert_eq!(coin_change(&[2], 3), None);
}
}
Deep Comparison
OCaml vs Rust: Coin Change Dp
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.
Exercises
coin_change_reconstruct(coins, amount) -> Option<Vec<usize>> that returns the actual coins used, not just the count.coin_change_limited(coins, counts, amount) where counts[i] limits how many times coin i can be used (bounded knapsack variant).