rust and zig : add codes for chapter_dynamic_programming (#606)

* rust : add codes for chapter_dynamic_programming

* zig : add codes for chapter_dynamic_programming

* rust : add codes for chapter_backtracking

* Update n_queens.rs

---------

Co-authored-by: Yudong Jin <krahets@163.com>
This commit is contained in:
sjinzh
2023-07-15 23:16:02 +08:00
committed by GitHub
parent 5c09add1ec
commit b1f8857212
27 changed files with 1654 additions and 6 deletions
@@ -7,7 +7,7 @@
/* 搜索 */
fn dfs(i: usize) -> i32 {
// 已知 dp[1] 和 dp[2] ,返回之
if i == 1 || i == 2 { return i as i32 };
if i == 1 || i == 2 { return i as i32; }
// dp[i] = dp[i-1] + dp[i-2]
let count = dfs(i - 1) + dfs(i - 2);
count
@@ -7,9 +7,9 @@
/* 记忆化搜索 */
fn dfs(i: usize, mem: &mut [i32]) -> i32 {
// 已知 dp[1] 和 dp[2] ,返回之
if i == 1 || i == 2 { return i as i32};
if i == 1 || i == 2 { return i as i32; }
// 若存在记录 dp[i] ,则直接返回之
if mem[i] != -1 { return mem[i] };
if mem[i] != -1 { return mem[i]; }
// dp[i] = dp[i-1] + dp[i-2]
let count = dfs(i - 1, mem) + dfs(i - 2, mem);
// 记录 dp[i]
@@ -7,7 +7,7 @@
/* 爬楼梯:动态规划 */
fn climbing_stairs_dp(n: usize) -> i32 {
// 已知 dp[1] 和 dp[2] ,返回之
if n == 1 || n == 2 { return n as i32 };
if n == 1 || n == 2 { return n as i32; }
// 初始化 dp 表,用于存储子问题的解
let mut dp = vec![-1; n + 1];
// 初始状态:预设最小子问题的解
@@ -22,7 +22,7 @@ fn climbing_stairs_dp(n: usize) -> i32 {
/* 爬楼梯:状态压缩后的动态规划 */
fn climbing_stairs_dp_comp(n: usize) -> i32 {
if n == 1 || n == 2 { return n as i32 };
if n == 1 || n == 2 { return n as i32; }
let (mut a, mut b) = (1, 2);
for _ in 3..=n {
let tmp = b;
@@ -0,0 +1,67 @@
/*
* File: coin_change.rs
* Created Time: 2023-07-09
* Author: sjinzh (sjinzh@gmail.com)
*/
/* 零钱兑换:动态规划 */
fn coin_change_dp(coins: &[i32], amt: usize) -> i32 {
let n = coins.len();
let max = amt + 1;
// 初始化 dp 表
let mut dp = vec![vec![0; amt + 1]; n + 1];
// 状态转移:首行首列
for a in 1..= amt {
dp[0][a] = max;
}
// 状态转移:其余行列
for i in 1..=n {
for a in 1..=amt {
if coins[i - 1] > a as i32 {
// 若超过背包容量,则不选硬币 i
dp[i][a] = dp[i - 1][a];
} else {
// 不选和选硬币 i 这两种方案的较小值
dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1);
}
}
}
if dp[n][amt] != max { return dp[n][amt] as i32; } else { -1 }
}
/* 零钱兑换:状态压缩后的动态规划 */
fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 {
let n = coins.len();
let max = amt + 1;
// 初始化 dp 表
let mut dp = vec![0; amt + 1];
dp.fill(max);
dp[0] = 0;
// 状态转移
for i in 1..=n {
for a in 1..=amt {
if coins[i - 1] > a as i32 {
// 若超过背包容量,则不选硬币 i
dp[a] = dp[a];
} else {
// 不选和选硬币 i 这两种方案的较小值
dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1);
}
}
}
if dp[amt] != max { return dp[amt] as i32; } else { -1 }
}
/* Driver Code */
pub fn main() {
let coins = [ 1, 2, 5 ];
let amt: usize = 4;
// 动态规划
let res = coin_change_dp(&coins, amt);
println!("凑到目标金额所需的最少硬币数量为 {res}");
// 状态压缩后的动态规划
let res = coin_change_dp_comp(&coins, amt);
println!("凑到目标金额所需的最少硬币数量为 {res}");
}
@@ -0,0 +1,64 @@
/*
* File: coin_change_ii.rs
* Created Time: 2023-07-09
* Author: sjinzh (sjinzh@gmail.com)
*/
/* 零钱兑换 II:动态规划 */
fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 {
let n = coins.len();
// 初始化 dp 表
let mut dp = vec![vec![0; amt + 1]; n + 1];
// 初始化首列
for i in 0..= n {
dp[i][0] = 1;
}
// 状态转移
for i in 1..=n {
for a in 1..=amt {
if coins[i - 1] > a as i32 {
// 若超过背包容量,则不选硬币 i
dp[i][a] = dp[i - 1][a];
} else {
// 不选和选硬币 i 这两种方案的较小值
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize];
}
}
}
dp[n][amt]
}
/* 零钱兑换 II:状态压缩后的动态规划 */
fn coin_change_dp_ii_comp(coins: &[i32], amt: usize) -> i32 {
let n = coins.len();
// 初始化 dp 表
let mut dp = vec![0; amt + 1];
dp[0] = 1;
// 状态转移
for i in 1..=n {
for a in 1..=amt {
if coins[i - 1] > a as i32 {
// 若超过背包容量,则不选硬币 i
dp[a] = dp[a];
} else {
// 不选和选硬币 i 这两种方案的较小值
dp[a] = dp[a] + dp[a - coins[i - 1] as usize];
}
}
}
dp[amt]
}
/* Driver Code */
pub fn main() {
let coins = [ 1, 2, 5 ];
let amt: usize = 5;
// 动态规划
let res = coin_change_ii_dp(&coins, amt);
println!("凑出目标金额的硬币组合数量为 {res}");
// 状态压缩后的动态规划
let res = coin_change_dp_ii_comp(&coins, amt);
println!("凑出目标金额的硬币组合数量为 {res}");
}
@@ -0,0 +1,130 @@
/*
* File: edit_distance.rs
* Created Time: 2023-07-09
* Author: sjinzh (sjinzh@gmail.com)
*/
/* 编辑距离:暴力搜索 */
fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 {
// 若 s 和 t 都为空,则返回 0
if i == 0 && j == 0 { return 0; }
// 若 s 为空,则返回 t 长度
if i == 0 { return j as i32; }
// 若 t 为空,则返回 s 长度
if j == 0 {return i as i32; }
// 若两字符相等,则直接跳过此两字符
if s.chars().nth(i - 1) == t.chars().nth(j - 1) {
return edit_distance_dfs(s, t, i - 1, j - 1);
}
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
let insert = edit_distance_dfs(s, t, i, j - 1);
let delete = edit_distance_dfs(s, t, i - 1, j);
let replace = edit_distance_dfs(s, t, i - 1, j - 1);
// 返回最少编辑步数
std::cmp::min(std::cmp::min(insert, delete), replace) + 1
}
/* 编辑距离:记忆化搜索 */
fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec<Vec<i32>>, i: usize, j: usize) -> i32 {
// 若 s 和 t 都为空,则返回 0
if i == 0 && j == 0 { return 0; }
// 若 s 为空,则返回 t 长度
if i == 0 { return j as i32; }
// 若 t 为空,则返回 s 长度
if j == 0 {return i as i32; }
// 若已有记录,则直接返回之
if mem[i][j] != -1 { return mem[i][j]; }
// 若两字符相等,则直接跳过此两字符
if s.chars().nth(i - 1) == t.chars().nth(j - 1) {
return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1);
}
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1);
let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j);
let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1);
// 记录并返回最少编辑步数
mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1;
mem[i][j]
}
/* 编辑距离:动态规划 */
fn edit_distance_dp(s: &str, t: &str) -> i32 {
let (n, m) = (s.len(), t.len());
let mut dp = vec![vec![0; m + 1]; n + 1];
// 状态转移:首行首列
for i in 1..= n {
dp[i][0] = i as i32;
}
for j in 1..m {
dp[0][j] = j as i32;
}
// 状态转移:其余行列
for i in 1..=n {
for j in 1..=m {
if s.chars().nth(i - 1) == t.chars().nth(j - 1) {
// 若两字符相等,则直接跳过此两字符
dp[i][j] = dp[i - 1][j - 1];
} else {
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
dp[i][j] = std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
}
}
}
dp[n][m]
}
/* 编辑距离:状态压缩后的动态规划 */
fn edit_distance_dp_comp(s: &str, t: &str) -> i32 {
let (n, m) = (s.len(), t.len());
let mut dp = vec![0; m + 1];
// 状态转移:首行
for j in 1..m {
dp[j] = j as i32;
}
// 状态转移:其余行
for i in 1..=n {
// 状态转移:首列
let mut leftup = dp[0]; // 暂存 dp[i-1, j-1]
dp[0] = i as i32;
// 状态转移:其余列
for j in 1..=m {
let temp = dp[j];
if s.chars().nth(i - 1) == t.chars().nth(j - 1) {
// 若两字符相等,则直接跳过此两字符
dp[j] = leftup;
} else {
// 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1;
}
leftup = temp; // 更新为下一轮的 dp[i-1, j-1]
}
}
dp[m]
}
/* Driver Code */
pub fn main() {
let s = "bag";
let t = "pack";
let (n, m) = (s.len(), t.len());
// 暴力搜索
let res = edit_distance_dfs(s, t, n, m);
println!("{s} 更改为 {t} 最少需要编辑 {res}");
// 记忆搜索
let mut mem = vec![vec![0; m + 1]; n + 1];
for row in mem.iter_mut() {
row.fill(-1);
}
let res = edit_distance_dfs_mem(s, t, &mut mem, n, m);
println!("{s} 更改为 {t} 最少需要编辑 {res}");
// 动态规划
let res = edit_distance_dp(s, t);
println!("{s} 更改为 {t} 最少需要编辑 {res}");
// 状态压缩后的动态规划
let res = edit_distance_dp_comp(s, t);
println!("{s} 更改为 {t} 最少需要编辑 {res}");
}
@@ -0,0 +1,110 @@
/*
* File: knapsack.rs
* Created Time: 2023-07-09
* Author: sjinzh (sjinzh@gmail.com)
*/
/* 0-1 背包:暴力搜索 */
fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 {
// 若已选完所有物品或背包无容量,则返回价值 0
if i == 0 || c == 0 {
return 0;
}
// 若超过背包容量,则只能不放入背包
if wgt[i - 1] > c as i32 {
return knapsack_dfs(wgt, val, i - 1, c);
}
// 计算不放入和放入物品 i 的最大价值
let no = knapsack_dfs(wgt, val, i - 1, c);
let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1];
// 返回两种方案中价值更大的那一个
std::cmp::max(no, yes)
}
/* 0-1 背包:记忆化搜索 */
fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec<Vec<i32>>, i: usize, c: usize) -> i32 {
// 若已选完所有物品或背包无容量,则返回价值 0
if i == 0 || c == 0 {
return 0;
}
// 若已有记录,则直接返回
if mem[i][c] != -1 {
return mem[i][c];
}
// 若超过背包容量,则只能不放入背包
if wgt[i - 1] > c as i32 {
return knapsack_dfs_mem(wgt, val, mem, i - 1, c);
}
// 计算不放入和放入物品 i 的最大价值
let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c);
let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1];
// 记录并返回两种方案中价值更大的那一个
mem[i][c] = std::cmp::max(no, yes);
mem[i][c]
}
/* 0-1 背包:动态规划 */
fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {
let n = wgt.len();
// 初始化 dp 表
let mut dp = vec![vec![0; cap + 1]; n + 1];
// 状态转移
for i in 1..=n {
for c in 1..=cap {
if wgt[i - 1] > c as i32 {
// 若超过背包容量,则不选物品 i
dp[i][c] = dp[i - 1][c];
} else {
// 不选和选物品 i 这两种方案的较大值
dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1]);
}
}
}
dp[n][cap]
}
/* 0-1 背包:状态压缩后的动态规划 */
fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {
let n = wgt.len();
// 初始化 dp 表
let mut dp = vec![0; cap + 1];
// 状态转移
for i in 1..=n {
// 倒序遍历
for c in (1..=cap).rev() {
if wgt[i - 1] <= c as i32 {
// 不选和选物品 i 这两种方案的较大值
dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);
}
}
}
dp[cap]
}
/* Driver Code */
pub fn main() {
let wgt = [ 10, 20, 30, 40, 50 ];
let val = [ 50, 120, 150, 210, 240 ];
let cap: usize = 50;
let n = wgt.len();
// 暴力搜索
let res = knapsack_dfs(&wgt, &val, n, cap);
println!("不超过背包容量的最大物品价值为 {res}");
// 记忆搜索
let mut mem = vec![vec![0; cap + 1]; n + 1];
for row in mem.iter_mut() {
row.fill(-1);
}
let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap);
println!("不超过背包容量的最大物品价值为 {res}");
// 动态规划
let res = knapsack_dp(&wgt, &val, cap);
println!("不超过背包容量的最大物品价值为 {res}");
// 状态压缩后的动态规划
let res = knapsack_dp_comp(&wgt, &val, cap);
println!("不超过背包容量的最大物品价值为 {res}");
}
@@ -9,7 +9,7 @@
/* 爬楼梯最小代价:动态规划 */
fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 {
let n = cost.len() - 1;
if n == 1 || n == 2 { return cost[n] };
if n == 1 || n == 2 { return cost[n]; }
// 初始化 dp 表,用于存储子问题的解
let mut dp = vec![-1; n + 1];
// 初始状态:预设最小子问题的解
@@ -0,0 +1,119 @@
/*
* File: min_path_sum.rs
* Created Time: 2023-07-09
* Author: sjinzh (sjinzh@gmail.com)
*/
/* 最小路径和:暴力搜索 */
fn min_path_sum_dfs(grid: &Vec<Vec<i32>>, i: i32, j: i32) -> i32 {
// 若为左上角单元格,则终止搜索
if i == 0 && j == 0 {
return grid[0][0];
}
// 若行列索引越界,则返回 +∞ 代价
if i < 0 || j < 0 {
return i32::MAX;
}
// 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价
let left = min_path_sum_dfs(grid, i - 1, j);
let up = min_path_sum_dfs(grid, i, j - 1);
// 返回从左上角到 (i, j) 的最小路径代价
std::cmp::min(left, up) + grid[i as usize][j as usize]
}
/* 最小路径和:记忆化搜索 */
fn min_path_sum_dfs_mem(grid: &Vec<Vec<i32>>, mem: &mut Vec<Vec<i32>>, i: i32, j: i32) -> i32 {
// 若为左上角单元格,则终止搜索
if i == 0 && j == 0 {
return grid[0][0];
}
// 若行列索引越界,则返回 +∞ 代价
if i < 0 || j < 0 {
return i32::MAX;
}
// 若已有记录,则直接返回
if mem[i as usize][j as usize] != -1 {
return mem[i as usize][j as usize];
}
// 左边和上边单元格的最小路径代价
let left = min_path_sum_dfs_mem(grid, mem, i - 1, j);
let up = min_path_sum_dfs_mem(grid, mem, i, j - 1);
// 记录并返回左上角到 (i, j) 的最小路径代价
mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize];
mem[i as usize][j as usize]
}
/* 最小路径和:动态规划 */
fn min_path_sum_dp(grid: &Vec<Vec<i32>>) -> i32 {
let (n, m) = (grid.len(), grid[0].len());
// 初始化 dp 表
let mut dp = vec![vec![0; m]; n];
dp[0][0] = grid[0][0];
// 状态转移:首行
for j in 1..m {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
// 状态转移:首列
for i in 1..n {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
// 状态转移:其余行列
for i in 1..n {
for j in 1..m {
dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];
}
}
dp[n - 1][m - 1]
}
/* 最小路径和:状态压缩后的动态规划 */
fn min_path_sum_dp_comp(grid: &Vec<Vec<i32>>) -> i32 {
let (n, m) = (grid.len(), grid[0].len());
// 初始化 dp 表
let mut dp = vec![0; m];
// 状态转移:首行
dp[0] = grid[0][0];
for j in 1..m {
dp[j] = dp[j - 1] + grid[0][j];
}
// 状态转移:其余行
for i in 1..n {
// 状态转移:首列
dp[0] = dp[0] + grid[i][0];
// 状态转移:其余列
for j in 1..m {
dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j];
}
}
dp[m - 1]
}
/* Driver Code */
pub fn main() {
let grid = vec![
vec![ 1, 3, 1, 5 ],
vec![ 2, 2, 4, 2 ],
vec![ 5, 3, 2, 1 ],
vec![ 4, 3, 5, 2 ]];
let (n, m) = (grid.len(), grid[0].len());
// 暴力搜索
let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1);
println!("从左上角到右下角的最小路径和为 {res}");
// 记忆化搜索
let mut mem = vec![vec![0; m]; n];
for row in mem.iter_mut() {
row.fill(-1);
}
let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1);
println!("从左上角到右下角的最小路径和为 {res}");
// 动态规划
let res = min_path_sum_dp(&grid);
println!("从左上角到右下角的最小路径和为 {res}");
// 状态压缩后的动态规划
let res = min_path_sum_dp_comp(&grid);
println!("从左上角到右下角的最小路径和为 {res}");
}
@@ -0,0 +1,60 @@
/*
* File: unbounded_knapsack.rs
* Created Time: 2023-07-09
* Author: sjinzh (sjinzh@gmail.com)
*/
/* 完全背包:动态规划 */
fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {
let n = wgt.len();
// 初始化 dp 表
let mut dp = vec![vec![0; cap + 1]; n + 1];
// 状态转移
for i in 1..=n {
for c in 1..=cap {
if wgt[i - 1] > c as i32 {
// 若超过背包容量,则不选物品 i
dp[i][c] = dp[i - 1][c];
} else {
// 不选和选物品 i 这两种方案的较大值
dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]);
}
}
}
return dp[n][cap];
}
/* 完全背包:状态压缩后的动态规划 */
fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {
let n = wgt.len();
// 初始化 dp 表
let mut dp = vec![0; cap + 1];
// 状态转移
for i in 1..=n {
for c in 1..=cap {
if wgt[i - 1] > c as i32 {
// 若超过背包容量,则不选物品 i
dp[c] = dp[c];
} else {
// 不选和选物品 i 这两种方案的较大值
dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);
}
}
}
dp[cap]
}
/* Driver Code */
pub fn main() {
let wgt = [ 1, 2, 3 ];
let val = [ 5, 11, 15 ];
let cap: usize = 4;
// 动态规划
let res = unbounded_knapsack_dp(&wgt, &val, cap);
println!("不超过背包容量的最大物品价值为 {res}");
// 状态压缩后的动态规划
let res = unbounded_knapsack_dp_comp(&wgt, &val, cap);
println!("不超过背包容量的最大物品价值为 {res}");
}