-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into optimize-btree-search
- Loading branch information
Showing
16 changed files
with
582 additions
and
290 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
//! This module provides functionality to check if there exists a subset of a given set of integers | ||
//! that sums to a target value. The implementation uses a recursive backtracking approach. | ||
|
||
/// Checks if there exists a subset of the given set that sums to the target value. | ||
pub fn has_subset_with_sum(set: &[isize], target: isize) -> bool { | ||
backtrack(set, set.len(), target) | ||
} | ||
|
||
fn backtrack(set: &[isize], remaining_items: usize, target: isize) -> bool { | ||
// Found a subset with the required sum | ||
if target == 0 { | ||
return true; | ||
} | ||
// No more elements to process | ||
if remaining_items == 0 { | ||
return false; | ||
} | ||
// Check if we can find a subset including or excluding the last element | ||
backtrack(set, remaining_items - 1, target) | ||
|| backtrack(set, remaining_items - 1, target - set[remaining_items - 1]) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
macro_rules! has_subset_with_sum_tests { | ||
($($name:ident: $test_case:expr,)*) => { | ||
$( | ||
#[test] | ||
fn $name() { | ||
let (set, target, expected) = $test_case; | ||
assert_eq!(has_subset_with_sum(set, target), expected); | ||
} | ||
)* | ||
} | ||
} | ||
|
||
has_subset_with_sum_tests! { | ||
test_small_set_with_sum: (&[3, 34, 4, 12, 5, 2], 9, true), | ||
test_small_set_without_sum: (&[3, 34, 4, 12, 5, 2], 30, false), | ||
test_consecutive_set_with_sum: (&[1, 2, 3, 4, 5, 6], 10, true), | ||
test_consecutive_set_without_sum: (&[1, 2, 3, 4, 5, 6], 22, false), | ||
test_large_set_with_sum: (&[5, 10, 12, 13, 15, 18, -1, 10, 50, -2, 3, 4], 30, true), | ||
test_empty_set: (&[], 0, true), | ||
test_empty_set_with_nonzero_sum: (&[], 10, false), | ||
test_single_element_equal_to_sum: (&[10], 10, true), | ||
test_single_element_not_equal_to_sum: (&[5], 10, false), | ||
test_negative_set_with_sum: (&[-7, -3, -2, 5, 8], 0, true), | ||
test_negative_sum: (&[1, 2, 3, 4, 5], -1, false), | ||
test_negative_sum_with_negatives: (&[-7, -3, -2, 5, 8], -4, true), | ||
test_negative_sum_with_negatives_no_solution: (&[-7, -3, -2, 5, 8], -14, false), | ||
test_even_inputs_odd_target: (&[2, 4, 6, 2, 8, -2, 10, 12, -24, 8, 12, 18], 3, false), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,94 @@ | ||
/// Coin change via Dynamic Programming | ||
//! This module provides a solution to the coin change problem using dynamic programming. | ||
//! The `coin_change` function calculates the fewest number of coins required to make up | ||
//! a given amount using a specified set of coin denominations. | ||
//! | ||
//! The implementation leverages dynamic programming to build up solutions for smaller | ||
//! amounts and combines them to solve for larger amounts. It ensures optimal substructure | ||
//! and overlapping subproblems are efficiently utilized to achieve the solution. | ||
|
||
/// coin_change(coins, amount) returns the fewest number of coins that need to make up that amount. | ||
/// If that amount of money cannot be made up by any combination of the coins, return `None`. | ||
//! # Complexity | ||
//! - Time complexity: O(amount * coins.length) | ||
//! - Space complexity: O(amount) | ||
|
||
/// Returns the fewest number of coins needed to make up the given amount using the provided coin denominations. | ||
/// If the amount cannot be made up by any combination of the coins, returns `None`. | ||
/// | ||
/// # Arguments | ||
/// * `coins` - A slice of coin denominations. | ||
/// * `amount` - The total amount of money to be made up. | ||
/// | ||
/// # Returns | ||
/// * `Option<usize>` - The minimum number of coins required to make up the amount, or `None` if it's not possible. | ||
/// | ||
/// # Arguments: | ||
/// * `coins` - coins of different denominations | ||
/// * `amount` - a total amount of money be made up. | ||
/// # Complexity | ||
/// - time complexity: O(amount * coins.length), | ||
/// - space complexity: O(amount), | ||
/// * Time complexity: O(amount * coins.length) | ||
/// * Space complexity: O(amount) | ||
pub fn coin_change(coins: &[usize], amount: usize) -> Option<usize> { | ||
let mut dp = vec![None; amount + 1]; | ||
dp[0] = Some(0); | ||
let mut min_coins = vec![None; amount + 1]; | ||
min_coins[0] = Some(0); | ||
|
||
// Assume dp[i] is the fewest number of coins making up amount i, | ||
// then for every coin in coins, dp[i] = min(dp[i - coin] + 1). | ||
for i in 0..=amount { | ||
for &coin in coins { | ||
if i >= coin { | ||
dp[i] = match dp[i - coin] { | ||
Some(prev_coins) => match dp[i] { | ||
Some(curr_coins) => Some(curr_coins.min(prev_coins + 1)), | ||
None => Some(prev_coins + 1), | ||
}, | ||
None => dp[i], | ||
}; | ||
} | ||
} | ||
} | ||
(0..=amount).for_each(|curr_amount| { | ||
coins | ||
.iter() | ||
.filter(|&&coin| curr_amount >= coin) | ||
.for_each(|&coin| { | ||
if let Some(prev_min_coins) = min_coins[curr_amount - coin] { | ||
min_coins[curr_amount] = Some( | ||
min_coins[curr_amount].map_or(prev_min_coins + 1, |curr_min_coins| { | ||
curr_min_coins.min(prev_min_coins + 1) | ||
}), | ||
); | ||
} | ||
}); | ||
}); | ||
|
||
dp[amount] | ||
min_coins[amount] | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn basic() { | ||
// 11 = 5 * 2 + 1 * 1 | ||
let coins = vec![1, 2, 5]; | ||
assert_eq!(Some(3), coin_change(&coins, 11)); | ||
|
||
// 119 = 11 * 10 + 7 * 1 + 2 * 1 | ||
let coins = vec![2, 3, 5, 7, 11]; | ||
assert_eq!(Some(12), coin_change(&coins, 119)); | ||
} | ||
|
||
#[test] | ||
fn coins_empty() { | ||
let coins = vec![]; | ||
assert_eq!(None, coin_change(&coins, 1)); | ||
} | ||
|
||
#[test] | ||
fn amount_zero() { | ||
let coins = vec![1, 2, 3]; | ||
assert_eq!(Some(0), coin_change(&coins, 0)); | ||
macro_rules! coin_change_tests { | ||
($($name:ident: $test_case:expr,)*) => { | ||
$( | ||
#[test] | ||
fn $name() { | ||
let (coins, amount, expected) = $test_case; | ||
assert_eq!(expected, coin_change(&coins, amount)); | ||
} | ||
)* | ||
} | ||
} | ||
|
||
#[test] | ||
fn fail_change() { | ||
// 3 can't be change by 2. | ||
let coins = vec![2]; | ||
assert_eq!(None, coin_change(&coins, 3)); | ||
let coins = vec![10, 20, 50, 100]; | ||
assert_eq!(None, coin_change(&coins, 5)); | ||
coin_change_tests! { | ||
test_basic_case: (vec![1, 2, 5], 11, Some(3)), | ||
test_multiple_denominations: (vec![2, 3, 5, 7, 11], 119, Some(12)), | ||
test_empty_coins: (vec![], 1, None), | ||
test_zero_amount: (vec![1, 2, 3], 0, Some(0)), | ||
test_no_solution_small_coin: (vec![2], 3, None), | ||
test_no_solution_large_coin: (vec![10, 20, 50, 100], 5, None), | ||
test_single_coin_large_amount: (vec![1], 100, Some(100)), | ||
test_large_amount_multiple_coins: (vec![1, 2, 5], 10000, Some(2000)), | ||
test_no_combination_possible: (vec![3, 7], 5, None), | ||
test_exact_combination: (vec![1, 3, 4], 6, Some(2)), | ||
test_large_denomination_multiple_coins: (vec![10, 50, 100], 1000, Some(10)), | ||
test_small_amount_not_possible: (vec![5, 10], 1, None), | ||
test_non_divisible_amount: (vec![2], 3, None), | ||
test_all_multiples: (vec![1, 2, 4, 8], 15, Some(4)), | ||
test_large_amount_mixed_coins: (vec![1, 5, 10, 25], 999, Some(45)), | ||
test_prime_coins_and_amount: (vec![2, 3, 5, 7], 17, Some(3)), | ||
test_coins_larger_than_amount: (vec![5, 10, 20], 1, None), | ||
test_repeating_denominations: (vec![1, 1, 1, 5], 8, Some(4)), | ||
test_non_standard_denominations: (vec![1, 4, 6, 9], 15, Some(2)), | ||
test_very_large_denominations: (vec![1000, 2000, 5000], 1, None), | ||
test_large_amount_performance: (vec![1, 5, 10, 25, 50, 100, 200, 500], 9999, Some(29)), | ||
test_powers_of_two: (vec![1, 2, 4, 8, 16, 32, 64], 127, Some(7)), | ||
test_fibonacci_sequence: (vec![1, 2, 3, 5, 8, 13, 21, 34], 55, Some(2)), | ||
test_mixed_small_large: (vec![1, 100, 1000, 10000], 11001, Some(3)), | ||
test_impossible_combinations: (vec![2, 4, 6, 8], 7, None), | ||
test_greedy_approach_does_not_work: (vec![1, 12, 20], 24, Some(2)), | ||
test_zero_denominations_no_solution: (vec![0], 1, None), | ||
test_zero_denominations_solution: (vec![0], 0, Some(0)), | ||
} | ||
} |
Oops, something went wrong.