feat: Traditional Chinese version (#1163)

* First commit

* Update mkdocs.yml

* Translate all the docs to traditional Chinese

* Translate the code files.

* Translate the docker file

* Fix mkdocs.yml

* Translate all the figures from SC to TC

* 二叉搜尋樹 -> 二元搜尋樹

* Update terminology.

* Update terminology

* 构造函数/构造方法 -> 建構子
异或 -> 互斥或

* 擴充套件 -> 擴展

* constant - 常量 - 常數

* 類	-> 類別

* AVL -> AVL 樹

* 數組 -> 陣列

* 係統 -> 系統
斐波那契數列 -> 費波那契數列
運算元量 -> 運算量
引數 -> 參數

* 聯絡 -> 關聯

* 麵試 -> 面試

* 面向物件 -> 物件導向
歸併排序 -> 合併排序
范式 -> 範式

* Fix 算法 -> 演算法

* 錶示 -> 表示
反碼 -> 一補數
補碼 -> 二補數
列列尾部 -> 佇列尾部
區域性性 -> 區域性
一摞 -> 一疊

* Synchronize with main branch

* 賬號 -> 帳號
推匯 -> 推導

* Sync with main branch

* First commit

* Update mkdocs.yml

* Translate all the docs to traditional Chinese

* Translate the code files.

* Translate the docker file

* Fix mkdocs.yml

* Translate all the figures from SC to TC

* 二叉搜尋樹 -> 二元搜尋樹

* Update terminology

* 构造函数/构造方法 -> 建構子
异或 -> 互斥或

* 擴充套件 -> 擴展

* constant - 常量 - 常數

* 類	-> 類別

* AVL -> AVL 樹

* 數組 -> 陣列

* 係統 -> 系統
斐波那契數列 -> 費波那契數列
運算元量 -> 運算量
引數 -> 參數

* 聯絡 -> 關聯

* 麵試 -> 面試

* 面向物件 -> 物件導向
歸併排序 -> 合併排序
范式 -> 範式

* Fix 算法 -> 演算法

* 錶示 -> 表示
反碼 -> 一補數
補碼 -> 二補數
列列尾部 -> 佇列尾部
區域性性 -> 區域性
一摞 -> 一疊

* Synchronize with main branch

* 賬號 -> 帳號
推匯 -> 推導

* Sync with main branch

* Update terminology.md

* 操作数量(num. of operations)-> 操作數量

* 字首和->前綴和

* Update figures

* 歸 -> 迴
記憶體洩漏 -> 記憶體流失

* Fix the bug of the file filter

* 支援 -> 支持
Add zh-Hant/README.md

* Add the zh-Hant chapter covers.
Bug fixes.

* 外掛 -> 擴充功能

* Add the landing page for zh-Hant version

* Unify the font of the chapter covers for the zh, en, and zh-Hant version

* Move zh-Hant/ to zh-hant/

* Translate terminology.md to traditional Chinese
This commit is contained in:
Yudong Jin
2024-04-06 02:30:11 +08:00
committed by GitHub
parent 33d7f8a2e5
commit 5f7385c8a3
1875 changed files with 102923 additions and 18 deletions
@@ -0,0 +1,41 @@
/*
* File: climbing_stairs_backtrack.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@gmail.com)
*/
/* 回溯 */
fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) {
// 當爬到第 n 階時,方案數量加 1
if state == n {
res[0] = res[0] + 1;
}
// 走訪所有選擇
for &choice in choices {
// 剪枝:不允許越過第 n 階
if state + choice > n {
continue;
}
// 嘗試:做出選擇,更新狀態
backtrack(choices, state + choice, n, res);
// 回退
}
}
/* 爬樓梯:回溯 */
fn climbing_stairs_backtrack(n: usize) -> i32 {
let choices = vec![1, 2]; // 可選擇向上爬 1 階或 2 階
let state = 0; // 從第 0 階開始爬
let mut res = Vec::new();
res.push(0); // 使用 res[0] 記錄方案數量
backtrack(&choices, state, n as i32, &mut res);
res[0]
}
/* Driver Code */
pub fn main() {
let n: usize = 9;
let res = climbing_stairs_backtrack(n);
println!("{n} 階樓梯共有 {res} 種方案");
}
@@ -0,0 +1,33 @@
/*
* File: climbing_stairs_constraint_dp.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@gmail.com)
*/
/* 帶約束爬樓梯:動態規劃 */
fn climbing_stairs_constraint_dp(n: usize) -> i32 {
if n == 1 || n == 2 {
return 1;
};
// 初始化 dp 表,用於儲存子問題的解
let mut dp = vec![vec![-1; 3]; n + 1];
// 初始狀態:預設最小子問題的解
dp[1][1] = 1;
dp[1][2] = 0;
dp[2][1] = 0;
dp[2][2] = 1;
// 狀態轉移:從較小子問題逐步求解較大子問題
for i in 3..=n {
dp[i][1] = dp[i - 1][2];
dp[i][2] = dp[i - 2][1] + dp[i - 2][2];
}
dp[n][1] + dp[n][2]
}
/* Driver Code */
pub fn main() {
let n: usize = 9;
let res = climbing_stairs_constraint_dp(n);
println!("{n} 階樓梯共有 {res} 種方案");
}
@@ -0,0 +1,29 @@
/*
* File: climbing_stairs_dfs.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@gmail.com)
*/
/* 搜尋 */
fn dfs(i: usize) -> i32 {
// 已知 dp[1] 和 dp[2] ,返回之
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
}
/* 爬樓梯:搜尋 */
fn climbing_stairs_dfs(n: usize) -> i32 {
dfs(n)
}
/* Driver Code */
pub fn main() {
let n: usize = 9;
let res = climbing_stairs_dfs(n);
println!("{n} 階樓梯共有 {res} 種方案");
}
@@ -0,0 +1,37 @@
/*
* File: climbing_stairs_dfs_mem.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@gmail.com)
*/
/* 記憶化搜尋 */
fn dfs(i: usize, mem: &mut [i32]) -> i32 {
// 已知 dp[1] 和 dp[2] ,返回之
if i == 1 || i == 2 {
return i as i32;
}
// 若存在記錄 dp[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]
mem[i] = count;
count
}
/* 爬樓梯:記憶化搜尋 */
fn climbing_stairs_dfs_mem(n: usize) -> i32 {
// mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄
let mut mem = vec![-1; n + 1];
dfs(n, &mut mem)
}
/* Driver Code */
pub fn main() {
let n: usize = 9;
let res = climbing_stairs_dfs_mem(n);
println!("{n} 階樓梯共有 {res} 種方案");
}
@@ -0,0 +1,48 @@
/*
* File: climbing_stairs_dp.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@gmail.com)
*/
/* 爬樓梯:動態規劃 */
fn climbing_stairs_dp(n: usize) -> i32 {
// 已知 dp[1] 和 dp[2] ,返回之
if n == 1 || n == 2 {
return n as i32;
}
// 初始化 dp 表,用於儲存子問題的解
let mut dp = vec![-1; n + 1];
// 初始狀態:預設最小子問題的解
dp[1] = 1;
dp[2] = 2;
// 狀態轉移:從較小子問題逐步求解較大子問題
for i in 3..=n {
dp[i] = dp[i - 1] + dp[i - 2];
}
dp[n]
}
/* 爬樓梯:空間最佳化後的動態規劃 */
fn climbing_stairs_dp_comp(n: usize) -> i32 {
if n == 1 || n == 2 {
return n as i32;
}
let (mut a, mut b) = (1, 2);
for _ in 3..=n {
let tmp = b;
b = a + b;
a = tmp;
}
b
}
/* Driver Code */
pub fn main() {
let n: usize = 9;
let res = climbing_stairs_dp(n);
println!("{n} 階樓梯共有 {res} 種方案");
let res = climbing_stairs_dp_comp(n);
println!("{n} 階樓梯共有 {res} 種方案");
}
@@ -0,0 +1,75 @@
/*
* File: coin_change.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@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: codingonion (coderonion@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_ii_dp_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_ii_dp_comp(&coins, amt);
println!("湊出目標金額的硬幣組合數量為 {res}");
}
@@ -0,0 +1,145 @@
/*
* File: edit_distance.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@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,113 @@
/*
* File: knapsack.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@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}");
}
@@ -0,0 +1,52 @@
/*
* File: min_cost_climbing_stairs_dp.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@gmail.com)
*/
use std::cmp;
/* 爬樓梯最小代價:動態規劃 */
fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 {
let n = cost.len() - 1;
if n == 1 || n == 2 {
return cost[n];
}
// 初始化 dp 表,用於儲存子問題的解
let mut dp = vec![-1; n + 1];
// 初始狀態:預設最小子問題的解
dp[1] = cost[1];
dp[2] = cost[2];
// 狀態轉移:從較小子問題逐步求解較大子問題
for i in 3..=n {
dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i];
}
dp[n]
}
/* 爬樓梯最小代價:空間最佳化後的動態規劃 */
fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 {
let n = cost.len() - 1;
if n == 1 || n == 2 {
return cost[n];
};
let (mut a, mut b) = (cost[1], cost[2]);
for i in 3..=n {
let tmp = b;
b = cmp::min(a, tmp) + cost[i];
a = tmp;
}
b
}
/* Driver Code */
pub fn main() {
let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1];
println!("輸入樓梯的代價串列為 {:?}", &cost);
let res = min_cost_climbing_stairs_dp(&cost);
println!("爬完樓梯的最低代價為 {res}");
let res = min_cost_climbing_stairs_dp_comp(&cost);
println!("爬完樓梯的最低代價為 {res}");
}
@@ -0,0 +1,120 @@
/*
* File: min_path_sum.rs
* Created Time: 2023-07-09
* Author: codingonion (coderonion@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 up = min_path_sum_dfs(grid, i - 1, j);
let left = 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 up = min_path_sum_dfs_mem(grid, mem, i - 1, j);
let left = 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: codingonion (coderonion@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}");
}