This commit is contained in:
krahets 2024-05-31 12:45:17 +08:00
parent 6bac0db1c4
commit 124c7ce24d
27 changed files with 1003 additions and 146 deletions

View File

@ -168,7 +168,7 @@ comments: true
```rust title="preorder_traversal_i_compact.rs"
/* 前序遍历:例题一 */
fn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<Rc<RefCell<TreeNode>>>) {
fn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<&Rc<RefCell<TreeNode>>>) {
if root.is_none() {
return;
}
@ -177,8 +177,8 @@ comments: true
// 记录解
res.push(node.clone());
}
pre_order(res, node.borrow().left.clone());
pre_order(res, node.borrow().right.clone());
pre_order(res, node.borrow().left.as_ref());
pre_order(res, node.borrow().right.as_ref());
}
}
```
@ -463,7 +463,7 @@ comments: true
fn pre_order(
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
path: &mut Vec<Rc<RefCell<TreeNode>>>,
root: Option<Rc<RefCell<TreeNode>>>,
root: Option<&Rc<RefCell<TreeNode>>>,
) {
if root.is_none() {
return;
@ -475,10 +475,10 @@ comments: true
// 记录解
res.push(path.clone());
}
pre_order(res, path, node.borrow().left.clone());
pre_order(res, path, node.borrow().right.clone());
pre_order(res, path, node.borrow().left.as_ref());
pre_order(res, path, node.borrow().right.as_ref());
// 回退
path.remove(path.len() - 1);
path.pop();
}
}
```
@ -819,7 +819,7 @@ comments: true
fn pre_order(
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
path: &mut Vec<Rc<RefCell<TreeNode>>>,
root: Option<Rc<RefCell<TreeNode>>>,
root: Option<&Rc<RefCell<TreeNode>>>,
) {
// 剪枝
if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {
@ -832,10 +832,10 @@ comments: true
// 记录解
res.push(path.clone());
}
pre_order(res, path, node.borrow().left.clone());
pre_order(res, path, node.borrow().right.clone());
pre_order(res, path, node.borrow().left.as_ref());
pre_order(res, path, node.borrow().right.as_ref());
// 回退
path.remove(path.len() - 1);
path.pop();
}
}
```
@ -1732,7 +1732,7 @@ comments: true
```rust title="preorder_traversal_iii_template.rs"
/* 判断当前状态是否为解 */
fn is_solution(state: &mut Vec<Rc<RefCell<TreeNode>>>) -> bool {
return !state.is_empty() && state.get(state.len() - 1).unwrap().borrow().val == 7;
return !state.is_empty() && state.last().unwrap().borrow().val == 7;
}
/* 记录解 */
@ -1744,8 +1744,8 @@ comments: true
}
/* 判断在当前状态下,该选择是否合法 */
fn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Rc<RefCell<TreeNode>>) -> bool {
return choice.borrow().val != 3;
fn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Option<&Rc<RefCell<TreeNode>>>) -> bool {
return choice.is_some() && choice.unwrap().borrow().val != 3;
}
/* 更新状态 */
@ -1755,13 +1755,13 @@ comments: true
/* 恢复状态 */
fn undo_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, _: Rc<RefCell<TreeNode>>) {
state.remove(state.len() - 1);
state.pop();
}
/* 回溯算法:例题三 */
fn backtrack(
state: &mut Vec<Rc<RefCell<TreeNode>>>,
choices: &mut Vec<Rc<RefCell<TreeNode>>>,
choices: &Vec<Option<&Rc<RefCell<TreeNode>>>>,
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
) {
// 检查是否为解
@ -1770,22 +1770,22 @@ comments: true
record_solution(state, res);
}
// 遍历所有选择
for choice in choices {
for &choice in choices.iter() {
// 剪枝:检查选择是否合法
if is_valid(state, choice.clone()) {
if is_valid(state, choice) {
// 尝试:做出选择,更新状态
make_choice(state, choice.clone());
make_choice(state, choice.unwrap().clone());
// 进行下一轮选择
backtrack(
state,
&mut vec![
choice.borrow().left.clone().unwrap(),
choice.borrow().right.clone().unwrap(),
&vec![
choice.unwrap().borrow().left.as_ref(),
choice.unwrap().borrow().right.as_ref(),
],
res,
);
// 回退:撤销选择,恢复到之前的状态
undo_choice(state, choice.clone());
undo_choice(state, choice.unwrap().clone());
}
}
}

View File

@ -11,7 +11,7 @@ comments: true
也就是说,在能够解决问题的前提下,算法效率已成为衡量算法优劣的主要评价指标,它包括以下两个维度。
- **时间效率**:算法运行速度的快慢
- **时间效率**:算法运行时间的长短
- **空间效率**:算法占用内存空间的大小。
简而言之,**我们的目标是设计“既快又省”的数据结构与算法**。而有效地评估算法效率至关重要,因为只有这样,我们才能将各种算法进行对比,进而指导算法设计与优化过程。
@ -22,7 +22,7 @@ comments: true
假设我们现在有算法 `A` 和算法 `B` ,它们都能解决同一问题,现在需要对比这两个算法的效率。最直接的方法是找一台计算机,运行这两个算法,并监控记录它们的运行时间和内存占用情况。这种评估方式能够反映真实情况,但也存在较大的局限性。
一方面,**难以排除测试环境的干扰因素**。硬件配置会影响算法的性能。比如在某台计算机中,算法 `A` 的运行时间比算法 `B` 短;但在另一台配置不同的计算机中,可能得到相反的测试结果。这意味着我们需要在各种机器上进行测试,统计平均效率,而这是不现实的。
一方面,**难以排除测试环境的干扰因素**。硬件配置会影响算法的性能表现。比如一个算法的并行度较高,那么它就更适合在多核 CPU 上运行,一个算法的内存操作密集,那么它在高性能内存上的表现就会更好。也就是说,算法在不同的机器上的测试结果可能是不一致的。这意味着我们需要在各种机器上进行测试,统计平均效率,而这是不现实的。
另一方面,**展开完整测试非常耗费资源**。随着输入数据量的变化,算法会表现出不同的效率。例如,在输入数据量较小时,算法 `A` 的运行时间比算法 `B` 短;而在输入数据量较大时,测试结果可能恰恰相反。因此,为了得到有说服力的结论,我们需要测试各种规模的输入数据,而这需要耗费大量的计算资源。
@ -36,8 +36,9 @@ comments: true
- “随着输入数据大小的增加”意味着复杂度反映了算法运行效率与输入数据体量之间的关系。
- “时间和空间的增长趋势”表示复杂度分析关注的不是运行时间或占用空间的具体值,而是时间或空间增长的“快慢”。
**复杂度分析克服了实际测试方法的弊端**,体现在以下个方面。
**复杂度分析克服了实际测试方法的弊端**,体现在以下个方面。
- 它无需实际运行代码,更加绿色节能。
- 它独立于测试环境,分析结果适用于所有运行平台。
- 它可以体现不同数据量下的算法效率,尤其是在大数据量下的算法性能。

View File

@ -540,7 +540,7 @@ $$
- **时间复杂度能够有效评估算法效率**。例如,算法 `B` 的运行时间呈线性增长,在 $n > 1$ 时比算法 `A` 更慢,在 $n > 1000000$ 时比算法 `C` 更慢。事实上,只要输入数据大小 $n$ 足够大,复杂度为“常数阶”的算法一定优于“线性阶”的算法,这正是时间增长趋势的含义。
- **时间复杂度的推算方法更简便**。显然,运行平台和计算操作类型都与算法运行时间的增长趋势无关。因此在时间复杂度分析中,我们可以简单地将所有计算操作的执行时间视为相同的“单位时间”,从而将“计算操作运行时间统计”简化为“计算操作数量统计”,这样一来估算难度就大大降低了。
- **时间复杂度也存在一定的局限性**。例如,尽管算法 `A``C` 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 `B` 的时间复杂度比 `C` 高,但在输入数据大小 $n$ 较小时,算法 `B` 明显优于算法 `C`在这些情况下,我们很难仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。
- **时间复杂度也存在一定的局限性**。例如,尽管算法 `A``C` 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 `B` 的时间复杂度比 `C` 高,但在输入数据大小 $n$ 较小时,算法 `B` 明显优于算法 `C`对于此类情况,我们时常难以仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。
## 2.3.2 &nbsp; 函数渐近上界
@ -3069,7 +3069,9 @@ $$
"""线性对数阶"""
if n <= 1:
return 1
count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2)
# 一分为二,子问题的规模减小一半
count = linear_log_recur(n // 2) + linear_log_recur(n // 2)
# 当前子问题包含 n 个操作
for _ in range(n):
count += 1
return count

View File

@ -39,7 +39,7 @@ comments: true
**Q**:原码转补码的方法是“先取反后加 1”那么补码转原码应该是逆运算“先减 1 后取反”,而补码转原码也一样可以通过“先取反后加 1”得到这是为什么呢
**A**这是因为原码和补码的相互转换实际上是计算“补数”的过程。我们先给出补数的定义:假设 $a + b = c$ ,那么我们称 $a$ 是 $b$ 到 $c$ 的补数,反之也称 $b$ 是 $a$ 到 $c$ 的补数。
这是因为原码和补码的相互转换实际上是计算“补数”的过程。我们先给出补数的定义:假设 $a + b = c$ ,那么我们称 $a$ 是 $b$ 到 $c$ 的补数,反之也称 $b$ 是 $a$ 到 $c$ 的补数。
给定一个 $n = 4$ 位长度的二进制数 $0010$ ,如果将这个数字看作原码(不考虑符号位),那么它的补码需通过“先取反后加 1”得到
@ -67,4 +67,4 @@ $$
本质上看,“取反”操作实际上是求到 $1111$ 的补数(因为恒有 `原码 + 反码 = 1111`);而在反码基础上再加 1 得到的补码,就是到 $10000$ 的补数。
上述 $n = 4$ 为例,其可推广至任意位数的二进制数。
上述 $n = 4$ 为例,其可推广至任意位数的二进制数。

View File

@ -304,7 +304,18 @@ $$
=== "Ruby"
```ruby title="min_cost_climbing_stairs_dp.rb"
[class]{}-[func]{min_cost_climbing_stairs_dp}
### 爬楼梯最小代价:动态规划 ###
def min_cost_climbing_stairs_dp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
# 初始化 dp 表,用于存储子问题的解
dp = Array.new(n + 1, 0)
# 初始状态:预设最小子问题的解
dp[1], dp[2] = cost[1], cost[2]
# 状态转移:从较小子问题逐步求解较大子问题
(3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }
dp[n]
end
```
=== "Zig"
@ -569,7 +580,27 @@ $$
=== "Ruby"
```ruby title="min_cost_climbing_stairs_dp.rb"
[class]{}-[func]{min_cost_climbing_stairs_dp_comp}
### 爬楼梯最小代价:动态规划 ###
def min_cost_climbing_stairs_dp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
# 初始化 dp 表,用于存储子问题的解
dp = Array.new(n + 1, 0)
# 初始状态:预设最小子问题的解
dp[1], dp[2] = cost[1], cost[2]
# 状态转移:从较小子问题逐步求解较大子问题
(3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }
dp[n]
end
# 爬楼梯最小代价:空间优化后的动态规划
def min_cost_climbing_stairs_dp_comp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
a, b = cost[1], cost[2]
(3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }
b
end
```
=== "Zig"
@ -935,7 +966,23 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_constraint_dp.rb"
[class]{}-[func]{climbing_stairs_constraint_dp}
### 带约束爬楼梯:动态规划 ###
def climbing_stairs_constraint_dp(n)
return 1 if n == 1 || n == 2
# 初始化 dp 表,用于存储子问题的解
dp = Array.new(n + 1) { Array.new(3, 0) }
# 初始状态:预设最小子问题的解
dp[1][1], dp[1][2] = 1, 0
dp[2][1], dp[2][2] = 0, 1
# 状态转移:从较小子问题逐步求解较大子问题
for i in 3...(n + 1)
dp[i][1] = dp[i - 1][2]
dp[i][2] = dp[i - 2][1] + dp[i - 2][2]
end
dp[n][1] + dp[n][2]
end
```
=== "Zig"

View File

@ -369,7 +369,18 @@ $$
=== "Ruby"
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dfs}
### 最小路径和:暴力搜索 ###
def min_path_sum_dfs(grid, i, j)
# 若为左上角单元格,则终止搜索
return grid[i][j] if i == 0 && j == 0
# 若行列索引越界,则返回 +∞ 代价
return Float::INFINITY if i < 0 || j < 0
# 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价
up = min_path_sum_dfs(grid, i - 1, j)
left = min_path_sum_dfs(grid, i, j - 1)
# 返回从左上角到 (i, j) 的最小路径代价
[left, up].min + grid[i][j]
end
```
=== "Zig"
@ -736,7 +747,20 @@ $$
=== "Ruby"
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dfs_mem}
### 最小路径和:记忆化搜索 ###
def min_path_sum_dfs_mem(grid, mem, i, j)
# 若为左上角单元格,则终止搜索
return grid[0][0] if i == 0 && j == 0
# 若行列索引越界,则返回 +∞ 代价
return Float::INFINITY if i < 0 || j < 0
# 若已有记录,则直接返回
return mem[i][j] if mem[i][j] != -1
# 左边和上边单元格的最小路径代价
up = min_path_sum_dfs_mem(grid, mem, i - 1, j)
left = min_path_sum_dfs_mem(grid, mem, i, j - 1)
# 记录并返回左上角到 (i, j) 的最小路径代价
mem[i][j] = [left, up].min + grid[i][j]
end
```
=== "Zig"
@ -1121,7 +1145,24 @@ $$
=== "Ruby"
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dp}
### 最小路径和:动态规划 ###
def min_path_sum_dp(grid)
n, m = grid.length, grid.first.length
# 初始化 dp 表
dp = Array.new(n) { Array.new(m, 0) }
dp[0][0] = grid[0][0]
# 状态转移:首行
(1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }
# 状态转移:首列
(1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }
# 状态转移:其余行和列
for i in 1...n
for j in 1...m
dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]
end
end
dp[n -1][m -1]
end
```
=== "Zig"
@ -1521,7 +1562,23 @@ $$
=== "Ruby"
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dp_comp}
### 最小路径和:空间优化后的动态规划 ###
def min_path_sum_dp_comp(grid)
n, m = grid.length, grid.first.length
# 初始化 dp 表
dp = Array.new(m, 0)
# 状态转移:首行
dp[0] = grid[0][0]
(1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }
# 状态转移:其余行
for i in 1...n
# 状态转移:首列
dp[0] = dp[0] + grid[i][0]
# 状态转移:其余列
(1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }
end
dp[m - 1]
end
```
=== "Zig"

View File

@ -454,7 +454,27 @@ $$
=== "Ruby"
```ruby title="edit_distance.rb"
[class]{}-[func]{edit_distance_dp}
### 编辑距离:动态规划 ###
def edit_distance_dp(s, t)
n, m = s.length, t.length
dp = Array.new(n + 1) { Array.new(m + 1, 0) }
# 状态转移:首行首列
(1...(n + 1)).each { |i| dp[i][0] = i }
(1...(m + 1)).each { |j| dp[0][j] = j }
# 状态转移:其余行和列
for i in 1...(n + 1)
for j in 1...(m +1)
if s[i - 1] == t[j - 1]
# 若两字符相等,则直接跳过此两字符
dp[i][j] = dp[i - 1][j - 1]
else
# 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1
end
end
end
dp[n][m]
end
```
=== "Zig"
@ -949,7 +969,32 @@ $$
=== "Ruby"
```ruby title="edit_distance.rb"
[class]{}-[func]{edit_distance_dp_comp}
### 编辑距离:空间优化后的动态规划 ###
def edit_distance_dp_comp(s, t)
n, m = s.length, t.length
dp = Array.new(m + 1, 0)
# 状态转移:首行
(1...(m + 1)).each { |j| dp[j] = j }
# 状态转移:其余行
for i in 1...(n + 1)
# 状态转移:首列
leftup = dp.first # 暂存 dp[i-1, j-1]
dp[0] += 1
# 状态转移:其余列
for j in 1...(m + 1)
temp = dp[j]
if s[i - 1] == t[j - 1]
# 若两字符相等,则直接跳过此两字符
dp[j] = leftup
else
# 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
dp[j] = [dp[j - 1], dp[j], leftup].min + 1
end
leftup = temp # 更新为下一轮的 dp[i-1, j-1]
end
end
dp[m]
end
```
=== "Zig"

View File

@ -395,9 +395,29 @@ comments: true
=== "Ruby"
```ruby title="climbing_stairs_backtrack.rb"
[class]{}-[func]{backtrack}
### 回溯 ###
def backtrack(choices, state, n, res)
# 当爬到第 n 阶时,方案数量加 1
res[0] += 1 if state == n
# 遍历所有选择
for choice in choices
# 剪枝:不允许越过第 n 阶
next if state + choice > n
[class]{}-[func]{climbing_stairs_backtrack}
# 尝试:做出选择,更新状态
backtrack(choices, state + choice, n, res)
end
# 回退
end
### 爬楼梯:回溯 ###
def climbing_stairs_backtrack(n)
choices = [1, 2] # 可选择向上爬 1 阶或 2 阶
state = 0 # 从第 0 阶开始爬
res = [0] # 使用 res[0] 记录方案数量
backtrack(choices, state, n, res)
res.first
end
```
=== "Zig"
@ -694,9 +714,18 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_dfs.rb"
[class]{}-[func]{dfs}
### 搜索 ###
def dfs(i)
# 已知 dp[1] 和 dp[2] ,返回之
return i if i == 1 || i == 2
# dp[i] = dp[i-1] + dp[i-2]
dfs(i - 1) + dfs(i - 2)
end
[class]{}-[func]{climbing_stairs_dfs}
### 爬楼梯:搜索 ###
def climbing_stairs_dfs(n)
dfs(n)
end
```
=== "Zig"
@ -1065,9 +1094,25 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_dfs_mem.rb"
[class]{}-[func]{dfs}
### 记忆化搜索 ###
def dfs(i, mem)
# 已知 dp[1] 和 dp[2] ,返回之
return i if i == 1 || i == 2
# 若存在记录 dp[i] ,则直接返回之
return mem[i] if mem[i] != -1
[class]{}-[func]{climbing_stairs_dfs_mem}
# dp[i] = dp[i-1] + dp[i-2]
count = dfs(i - 1, mem) + dfs(i - 2, mem)
# 记录 dp[i]
mem[i] = count
end
### 爬楼梯:记忆化搜索 ###
def climbing_stairs_dfs_mem(n)
# mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录
mem = Array.new(n + 1, -1)
dfs(n, mem)
end
```
=== "Zig"
@ -1359,7 +1404,19 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_dp.rb"
[class]{}-[func]{climbing_stairs_dp}
### 爬楼梯:动态规划 ###
def climbing_stairs_dp(n)
return n if n == 1 || n == 2
# 初始化 dp 表,用于存储子问题的解
dp = Array.new(n + 1, 0)
# 初始状态:预设最小子问题的解
dp[1], dp[2] = 1, 2
# 状态转移:从较小子问题逐步求解较大子问题
(3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }
dp[n]
end
```
=== "Zig"
@ -1610,7 +1667,15 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_dp.rb"
[class]{}-[func]{climbing_stairs_dp_comp}
### 爬楼梯:空间优化后的动态规划 ###
def climbing_stairs_dp_comp(n)
return n if n == 1 || n == 2
a, b = 1, 2
(3...(n + 1)).each { a, b = b, a + b }
b
end
```
=== "Zig"

View File

@ -324,7 +324,18 @@ $$
=== "Ruby"
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dfs}
### 0-1 背包:暴力搜索 ###
def knapsack_dfs(wgt, val, i, c)
# 若已选完所有物品或背包无剩余容量,则返回价值 0
return 0 if i == 0 || c == 0
# 若超过背包容量,则只能选择不放入背包
return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c
# 计算不放入和放入物品 i 的最大价值
no = knapsack_dfs(wgt, val, i - 1, c)
yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]
# 返回两种方案中价值更大的那一个
[no, yes].max
end
```
=== "Zig"
@ -700,7 +711,20 @@ $$
=== "Ruby"
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dfs_mem}
### 0-1 背包:记忆化搜索 ###
def knapsack_dfs_mem(wgt, val, mem, i, c)
# 若已选完所有物品或背包无剩余容量,则返回价值 0
return 0 if i == 0 || c == 0
# 若已有记录,则直接返回
return mem[i][c] if mem[i][c] != -1
# 若超过背包容量,则只能选择不放入背包
return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c
# 计算不放入和放入物品 i 的最大价值
no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)
yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]
# 记录并返回两种方案中价值更大的那一个
mem[i][c] = [no, yes].max
end
```
=== "Zig"
@ -1059,7 +1083,25 @@ $$
=== "Ruby"
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dp}
### 0-1 背包:动态规划 ###
def knapsack_dp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# 状态转移
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# 若超过背包容量,则不选物品 i
dp[i][c] = dp[i - 1][c]
else
# 不选和选物品 i 这两种方案的较大值
dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[n][cap]
end
```
=== "Zig"
@ -1446,7 +1488,26 @@ $$
=== "Ruby"
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dp_comp}
### 0-1 背包:空间优化后的动态规划 ###
def knapsack_dp_comp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(cap + 1, 0)
# 状态转移
for i in 1...(n + 1)
# 倒序遍历
for c in cap.downto(1)
if wgt[i - 1] > c
# 若超过背包容量,则不选物品 i
dp[c] = dp[c]
else
# 不选和选物品 i 这两种方案的较大值
dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[cap]
end
```
=== "Zig"

View File

@ -350,7 +350,25 @@ $$
=== "Ruby"
```ruby title="unbounded_knapsack.rb"
[class]{}-[func]{unbounded_knapsack_dp}
### 完全背包:动态规划 ###
def unbounded_knapsack_dp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# 状态转移
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# 若超过背包容量,则不选物品 i
dp[i][c] = dp[i - 1][c]
else
# 不选和选物品 i 这两种方案的较大值
dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[n][cap]
end
```
=== "Zig"
@ -709,7 +727,46 @@ $$
=== "Ruby"
```ruby title="unbounded_knapsack.rb"
[class]{}-[func]{unbounded_knapsack_dp_comp}
### 完全背包:动态规划 ###
def unbounded_knapsack_dp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# 状态转移
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# 若超过背包容量,则不选物品 i
dp[i][c] = dp[i - 1][c]
else
# 不选和选物品 i 这两种方案的较大值
dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[n][cap]
end
### 完全背包:空间优化后的动态规划 ##3
def unbounded_knapsack_dp_comp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(cap + 1, 0)
# 状态转移
for i in 1...(n + 1)
# 正序遍历
for c in 1...(cap + 1)
if wgt[i -1] > c
# 若超过背包容量,则不选物品 i
dp[c] = dp[c]
else
# 不选和选物品 i 这两种方案的较大值
dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[cap]
end
```
=== "Zig"
@ -1159,7 +1216,28 @@ $$
=== "Ruby"
```ruby title="coin_change.rb"
[class]{}-[func]{coin_change_dp}
### 零钱兑换:动态规划 ###
def coin_change_dp(coins, amt)
n = coins.length
_MAX = amt + 1
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
# 状态转移:首行首列
(1...(amt + 1)).each { |a| dp[0][a] = _MAX }
# 状态转移:其余行和列
for i in 1...(n + 1)
for a in 1...(amt + 1)
if coins[i - 1] > a
# 若超过目标金额,则不选硬币 i
dp[i][a] = dp[i - 1][a]
else
# 不选和选硬币 i 这两种方案的较小值
dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min
end
end
end
dp[n][amt] != _MAX ? dp[n][amt] : -1
end
```
=== "Zig"
@ -1586,7 +1664,28 @@ $$
=== "Ruby"
```ruby title="coin_change.rb"
[class]{}-[func]{coin_change_dp_comp}
### 零钱兑换:空间优化后的动态规划 ###
def coin_change_dp_comp(coins, amt)
n = coins.length
_MAX = amt + 1
# 初始化 dp 表
dp = Array.new(amt + 1, _MAX)
dp[0] = 0
# 状态转移
for i in 1...(n + 1)
# 正序遍历
for a in 1...(amt + 1)
if coins[i - 1] > a
# 若超过目标金额,则不选硬币 i
dp[a] = dp[a]
else
# 不选和选硬币 i 这两种方案的较小值
dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min
end
end
end
dp[amt] != _MAX ? dp[amt] : -1
end
```
=== "Zig"
@ -1999,7 +2098,27 @@ $$
=== "Ruby"
```ruby title="coin_change_ii.rb"
[class]{}-[func]{coin_change_ii_dp}
### 零钱兑换 II动态规划 ###
def coin_change_ii_dp(coins, amt)
n = coins.length
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
# 初始化首列
(0...(n + 1)).each { |i| dp[i][0] = 1 }
# 状态转移
for i in 1...(n + 1)
for a in 1...(amt + 1)
if coins[i - 1] > a
# 若超过目标金额,则不选硬币 i
dp[i][a] = dp[i - 1][a]
else
# 不选和选硬币 i 这两种方案之和
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]
end
end
end
dp[n][amt]
end
```
=== "Zig"
@ -2343,7 +2462,27 @@ $$
=== "Ruby"
```ruby title="coin_change_ii.rb"
[class]{}-[func]{coin_change_ii_dp_comp}
### 零钱兑换 II空间优化后的动态规划 ###
def coin_change_ii_dp_comp(coins, amt)
n = coins.length
# 初始化 dp 表
dp = Array.new(amt + 1, 0)
dp[0] = 1
# 状态转移
for i in 1...(n + 1)
# 正序遍历
for a in 1...(amt + 1)
if coins[i - 1] > a
# 若超过目标金额,则不选硬币 i
dp[a] = dp[a]
else
# 不选和选硬币 i 这两种方案之和
dp[a] = dp[a] + dp[a - coins[i - 1]]
end
end
end
dp[amt]
end
```
=== "Zig"

View File

@ -311,7 +311,15 @@ comments: true
=== "Ruby"
```ruby title="my_heap.rb"
[class]{MaxHeap}-[func]{__init__}
### 构造方法,根据输入列表建堆 ###
def initialize(nums)
# 将列表元素原封不动添加进堆
@max_heap = nums
# 堆化除叶节点以外的其他所有节点
parent(size - 1).downto(0) do |i|
sift_down(i)
end
end
```
=== "Zig"

View File

@ -132,17 +132,17 @@ comments: true
Queue<Integer> minHeap = new PriorityQueue<>();
// 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可)
Queue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
/* 元素入堆 */
maxHeap.offer(1);
maxHeap.offer(3);
maxHeap.offer(2);
maxHeap.offer(5);
maxHeap.offer(4);
/* 获取堆顶元素 */
int peek = maxHeap.peek(); // 5
/* 堆顶元素出堆 */
// 出堆元素会形成一个从大到小的序列
peek = maxHeap.poll(); // 5
@ -150,13 +150,13 @@ comments: true
peek = maxHeap.poll(); // 3
peek = maxHeap.poll(); // 2
peek = maxHeap.poll(); // 1
/* 获取堆大小 */
int size = maxHeap.size();
/* 判断堆是否为空 */
boolean isEmpty = maxHeap.isEmpty();
/* 输入列表并建堆 */
minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));
```
@ -347,7 +347,7 @@ comments: true
max_heap.push(2);
max_heap.push(5);
max_heap.push(4);
/* 获取堆顶元素 */
let peek = max_heap.peek().unwrap(); // 5
@ -383,17 +383,17 @@ comments: true
var minHeap = PriorityQueue<Int>()
// 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可)
val maxHeap = PriorityQueue { a: Int, b: Int -> b - a }
/* 元素入堆 */
maxHeap.offer(1)
maxHeap.offer(3)
maxHeap.offer(2)
maxHeap.offer(5)
maxHeap.offer(4)
/* 获取堆顶元素 */
var peek = maxHeap.peek() // 5
/* 堆顶元素出堆 */
// 出堆元素会形成一个从大到小的序列
peek = maxHeap.poll() // 5
@ -401,13 +401,13 @@ comments: true
peek = maxHeap.poll() // 3
peek = maxHeap.poll() // 2
peek = maxHeap.poll() // 1
/* 获取堆大小 */
val size = maxHeap.size
/* 判断堆是否为空 */
val isEmpty = maxHeap.isEmpty()
/* 输入列表并建堆 */
minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))
```
@ -415,7 +415,7 @@ comments: true
=== "Ruby"
```ruby title="heap.rb"
# Ruby 未提供内置 Heap 类
```
=== "Zig"

View File

@ -322,6 +322,13 @@
<br><sub>JS, TS</sub>
</a>
</div>
<div class="profile-cell">
<a href="https://github.com/khoaxuantu">
<img class="profile-img" src="assets/avatar/avatar_khoaxuantu.jpg" alt="Reviewer: khoaxuantu" />
<br><b>khoaxuantu</b>
<br><sub>Ruby</sub>
</a>
</div>
<div class="profile-cell">
<a href="https://github.com/krahets">
<img class="profile-img" src="assets/avatar/avatar_krahets.jpg" alt="Reviewer: krahets" />

View File

@ -124,7 +124,7 @@ It's worth mentioning that **since leaf nodes have no children, they naturally f
=== "Ruby"
```ruby title="my_heap.rb"
[class]{MaxHeap}-[func]{__init__}
[class]{MaxHeap}-[func]{initialize}
```
=== "Zig"

View File

@ -461,7 +461,7 @@ a:hover .text-button span {
.profile-cell {
flex: 1; /* even distribution */
flex-basis: 15%;
flex-basis: 20%;
margin: 1em 0.5em;
text-align: center;
}

View File

@ -168,7 +168,7 @@ comments: true
```rust title="preorder_traversal_i_compact.rs"
/* 前序走訪:例題一 */
fn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<Rc<RefCell<TreeNode>>>) {
fn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<&Rc<RefCell<TreeNode>>>) {
if root.is_none() {
return;
}
@ -177,8 +177,8 @@ comments: true
// 記錄解
res.push(node.clone());
}
pre_order(res, node.borrow().left.clone());
pre_order(res, node.borrow().right.clone());
pre_order(res, node.borrow().left.as_ref());
pre_order(res, node.borrow().right.as_ref());
}
}
```
@ -463,7 +463,7 @@ comments: true
fn pre_order(
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
path: &mut Vec<Rc<RefCell<TreeNode>>>,
root: Option<Rc<RefCell<TreeNode>>>,
root: Option<&Rc<RefCell<TreeNode>>>,
) {
if root.is_none() {
return;
@ -475,10 +475,10 @@ comments: true
// 記錄解
res.push(path.clone());
}
pre_order(res, path, node.borrow().left.clone());
pre_order(res, path, node.borrow().right.clone());
pre_order(res, path, node.borrow().left.as_ref());
pre_order(res, path, node.borrow().right.as_ref());
// 回退
path.remove(path.len() - 1);
path.pop();
}
}
```
@ -819,7 +819,7 @@ comments: true
fn pre_order(
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
path: &mut Vec<Rc<RefCell<TreeNode>>>,
root: Option<Rc<RefCell<TreeNode>>>,
root: Option<&Rc<RefCell<TreeNode>>>,
) {
// 剪枝
if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {
@ -832,10 +832,10 @@ comments: true
// 記錄解
res.push(path.clone());
}
pre_order(res, path, node.borrow().left.clone());
pre_order(res, path, node.borrow().right.clone());
pre_order(res, path, node.borrow().left.as_ref());
pre_order(res, path, node.borrow().right.as_ref());
// 回退
path.remove(path.len() - 1);
path.pop();
}
}
```
@ -1732,7 +1732,7 @@ comments: true
```rust title="preorder_traversal_iii_template.rs"
/* 判斷當前狀態是否為解 */
fn is_solution(state: &mut Vec<Rc<RefCell<TreeNode>>>) -> bool {
return !state.is_empty() && state.get(state.len() - 1).unwrap().borrow().val == 7;
return !state.is_empty() && state.last().unwrap().borrow().val == 7;
}
/* 記錄解 */
@ -1744,8 +1744,8 @@ comments: true
}
/* 判斷在當前狀態下,該選擇是否合法 */
fn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Rc<RefCell<TreeNode>>) -> bool {
return choice.borrow().val != 3;
fn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Option<&Rc<RefCell<TreeNode>>>) -> bool {
return choice.is_some() && choice.unwrap().borrow().val != 3;
}
/* 更新狀態 */
@ -1755,13 +1755,13 @@ comments: true
/* 恢復狀態 */
fn undo_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, _: Rc<RefCell<TreeNode>>) {
state.remove(state.len() - 1);
state.pop();
}
/* 回溯演算法:例題三 */
fn backtrack(
state: &mut Vec<Rc<RefCell<TreeNode>>>,
choices: &mut Vec<Rc<RefCell<TreeNode>>>,
choices: &Vec<Option<&Rc<RefCell<TreeNode>>>>,
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
) {
// 檢查是否為解
@ -1770,22 +1770,22 @@ comments: true
record_solution(state, res);
}
// 走訪所有選擇
for choice in choices {
for &choice in choices.iter() {
// 剪枝:檢查選擇是否合法
if is_valid(state, choice.clone()) {
if is_valid(state, choice) {
// 嘗試:做出選擇,更新狀態
make_choice(state, choice.clone());
make_choice(state, choice.unwrap().clone());
// 進行下一輪選擇
backtrack(
state,
&mut vec![
choice.borrow().left.clone().unwrap(),
choice.borrow().right.clone().unwrap(),
&vec![
choice.unwrap().borrow().left.as_ref(),
choice.unwrap().borrow().right.as_ref(),
],
res,
);
// 回退:撤銷選擇,恢復到之前的狀態
undo_choice(state, choice.clone());
undo_choice(state, choice.unwrap().clone());
}
}
}

View File

@ -11,7 +11,7 @@ comments: true
也就是說,在能夠解決問題的前提下,演算法效率已成為衡量演算法優劣的主要評價指標,它包括以下兩個維度。
- **時間效率**:演算法執行速度的快慢
- **時間效率**:演算法執行時間的長短
- **空間效率**:演算法佔用記憶體空間的大小。
簡而言之,**我們的目標是設計“既快又省”的資料結構與演算法**。而有效地評估演算法效率至關重要,因為只有這樣,我們才能將各種演算法進行對比,進而指導演算法設計與最佳化過程。
@ -22,7 +22,7 @@ comments: true
假設我們現在有演算法 `A` 和演算法 `B` ,它們都能解決同一問題,現在需要對比這兩個演算法的效率。最直接的方法是找一臺計算機,執行這兩個演算法,並監控記錄它們的執行時間和記憶體佔用情況。這種評估方式能夠反映真實情況,但也存在較大的侷限性。
一方面,**難以排除測試環境的干擾因素**。硬體配置會影響演算法的效能。比如在某臺計算機中,演算法 `A` 的執行時間比演算法 `B` 短;但在另一臺配置不同的計算機中,可能得到相反的測試結果。這意味著我們需要在各種機器上進行測試,統計平均效率,而這是不現實的。
一方面,**難以排除測試環境的干擾因素**。硬體配置會影響演算法的效能表現。比如一個演算法的並行度較高,那麼它就更適合在多核 CPU 上執行,一個演算法的記憶體操作密集,那麼它在高效能記憶體上的表現就會更好。也就是說,演算法在不同的機器上的測試結果可能是不一致的。這意味著我們需要在各種機器上進行測試,統計平均效率,而這是不現實的。
另一方面,**展開完整測試非常耗費資源**。隨著輸入資料量的變化,演算法會表現出不同的效率。例如,在輸入資料量較小時,演算法 `A` 的執行時間比演算法 `B` 短;而在輸入資料量較大時,測試結果可能恰恰相反。因此,為了得到有說服力的結論,我們需要測試各種規模的輸入資料,而這需要耗費大量的計算資源。
@ -36,8 +36,9 @@ comments: true
- “隨著輸入資料大小的增加”意味著複雜度反映了演算法執行效率與輸入資料體量之間的關係。
- “時間和空間的增長趨勢”表示複雜度分析關注的不是執行時間或佔用空間的具體值,而是時間或空間增長的“快慢”。
**複雜度分析克服了實際測試方法的弊端**,體現在以下個方面。
**複雜度分析克服了實際測試方法的弊端**,體現在以下個方面。
- 它無需實際執行程式碼,更加綠色節能。
- 它獨立於測試環境,分析結果適用於所有執行平臺。
- 它可以體現不同資料量下的演算法效率,尤其是在大資料量下的演算法效能。

View File

@ -540,7 +540,7 @@ $$
- **時間複雜度能夠有效評估演算法效率**。例如,演算法 `B` 的執行時間呈線性增長,在 $n > 1$ 時比演算法 `A` 更慢,在 $n > 1000000$ 時比演算法 `C` 更慢。事實上,只要輸入資料大小 $n$ 足夠大,複雜度為“常數階”的演算法一定優於“線性階”的演算法,這正是時間增長趨勢的含義。
- **時間複雜度的推算方法更簡便**。顯然,執行平臺和計算操作型別都與演算法執行時間的增長趨勢無關。因此在時間複雜度分析中,我們可以簡單地將所有計算操作的執行時間視為相同的“單位時間”,從而將“計算操作執行時間統計”簡化為“計算操作數量統計”,這樣一來估算難度就大大降低了。
- **時間複雜度也存在一定的侷限性**。例如,儘管演算法 `A``C` 的時間複雜度相同,但實際執行時間差別很大。同樣,儘管演算法 `B` 的時間複雜度比 `C` 高,但在輸入資料大小 $n$ 較小時,演算法 `B` 明顯優於演算法 `C`在這些情況下,我們很難僅憑時間複雜度判斷演算法效率的高低。當然,儘管存在上述問題,複雜度分析仍然是評判演算法效率最有效且常用的方法。
- **時間複雜度也存在一定的侷限性**。例如,儘管演算法 `A``C` 的時間複雜度相同,但實際執行時間差別很大。同樣,儘管演算法 `B` 的時間複雜度比 `C` 高,但在輸入資料大小 $n$ 較小時,演算法 `B` 明顯優於演算法 `C`對於此類情況,我們時常難以僅憑時間複雜度判斷演算法效率的高低。當然,儘管存在上述問題,複雜度分析仍然是評判演算法效率最有效且常用的方法。
## 2.3.2 &nbsp; 函式漸近上界
@ -3069,7 +3069,9 @@ $$
"""線性對數階"""
if n <= 1:
return 1
count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2)
# 一分為二,子問題的規模減小一半
count = linear_log_recur(n // 2) + linear_log_recur(n // 2)
# 當前子問題包含 n 個操作
for _ in range(n):
count += 1
return count

View File

@ -39,7 +39,7 @@ comments: true
**Q**:原碼轉二補數的方法是“先取反後加 1”那麼二補數轉原碼應該是逆運算“先減 1 後取反”,而二補數轉原碼也一樣可以透過“先取反後加 1”得到這是為什麼呢
**A**這是因為原碼和二補數的相互轉換實際上是計算“補數”的過程。我們先給出補數的定義:假設 $a + b = c$ ,那麼我們稱 $a$ 是 $b$ 到 $c$ 的補數,反之也稱 $b$ 是 $a$ 到 $c$ 的補數。
這是因為原碼和二補數的相互轉換實際上是計算“補數”的過程。我們先給出補數的定義:假設 $a + b = c$ ,那麼我們稱 $a$ 是 $b$ 到 $c$ 的補數,反之也稱 $b$ 是 $a$ 到 $c$ 的補數。
給定一個 $n = 4$ 位長度的二進位制數 $0010$ ,如果將這個數字看作原碼(不考慮符號位),那麼它的二補數需透過“先取反後加 1”得到
@ -67,4 +67,4 @@ $$
本質上看,“取反”操作實際上是求到 $1111$ 的補數(因為恆有 `原碼 + 一補數 = 1111`);而在一補數基礎上再加 1 得到的二補數,就是到 $10000$ 的補數。
上述 $n = 4$ 為例,其可推廣至任意位數的二進位制數。
上述 $n = 4$ 為例,其可推廣至任意位數的二進位制數。

View File

@ -304,7 +304,18 @@ $$
=== "Ruby"
```ruby title="min_cost_climbing_stairs_dp.rb"
[class]{}-[func]{min_cost_climbing_stairs_dp}
### 爬樓梯最小代價:動態規劃 ###
def min_cost_climbing_stairs_dp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
# 初始化 dp 表,用於儲存子問題的解
dp = Array.new(n + 1, 0)
# 初始狀態:預設最小子問題的解
dp[1], dp[2] = cost[1], cost[2]
# 狀態轉移:從較小子問題逐步求解較大子問題
(3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }
dp[n]
end
```
=== "Zig"
@ -569,7 +580,27 @@ $$
=== "Ruby"
```ruby title="min_cost_climbing_stairs_dp.rb"
[class]{}-[func]{min_cost_climbing_stairs_dp_comp}
### 爬樓梯最小代價:動態規劃 ###
def min_cost_climbing_stairs_dp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
# 初始化 dp 表,用於儲存子問題的解
dp = Array.new(n + 1, 0)
# 初始狀態:預設最小子問題的解
dp[1], dp[2] = cost[1], cost[2]
# 狀態轉移:從較小子問題逐步求解較大子問題
(3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }
dp[n]
end
# 爬樓梯最小代價:空間最佳化後的動態規劃
def min_cost_climbing_stairs_dp_comp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
a, b = cost[1], cost[2]
(3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }
b
end
```
=== "Zig"
@ -935,7 +966,23 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_constraint_dp.rb"
[class]{}-[func]{climbing_stairs_constraint_dp}
### 帶約束爬樓梯:動態規劃 ###
def climbing_stairs_constraint_dp(n)
return 1 if n == 1 || n == 2
# 初始化 dp 表,用於儲存子問題的解
dp = Array.new(n + 1) { Array.new(3, 0) }
# 初始狀態:預設最小子問題的解
dp[1][1], dp[1][2] = 1, 0
dp[2][1], dp[2][2] = 0, 1
# 狀態轉移:從較小子問題逐步求解較大子問題
for i in 3...(n + 1)
dp[i][1] = dp[i - 1][2]
dp[i][2] = dp[i - 2][1] + dp[i - 2][2]
end
dp[n][1] + dp[n][2]
end
```
=== "Zig"

View File

@ -369,7 +369,18 @@ $$
=== "Ruby"
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dfs}
### 最小路徑和:暴力搜尋 ###
def min_path_sum_dfs(grid, i, j)
# 若為左上角單元格,則終止搜尋
return grid[i][j] if i == 0 && j == 0
# 若行列索引越界,則返回 +∞ 代價
return Float::INFINITY if i < 0 || j < 0
# 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價
up = min_path_sum_dfs(grid, i - 1, j)
left = min_path_sum_dfs(grid, i, j - 1)
# 返回從左上角到 (i, j) 的最小路徑代價
[left, up].min + grid[i][j]
end
```
=== "Zig"
@ -736,7 +747,20 @@ $$
=== "Ruby"
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dfs_mem}
### 最小路徑和:記憶化搜尋 ###
def min_path_sum_dfs_mem(grid, mem, i, j)
# 若為左上角單元格,則終止搜尋
return grid[0][0] if i == 0 && j == 0
# 若行列索引越界,則返回 +∞ 代價
return Float::INFINITY if i < 0 || j < 0
# 若已有記錄,則直接返回
return mem[i][j] if mem[i][j] != -1
# 左邊和上邊單元格的最小路徑代價
up = min_path_sum_dfs_mem(grid, mem, i - 1, j)
left = min_path_sum_dfs_mem(grid, mem, i, j - 1)
# 記錄並返回左上角到 (i, j) 的最小路徑代價
mem[i][j] = [left, up].min + grid[i][j]
end
```
=== "Zig"
@ -1121,7 +1145,24 @@ $$
=== "Ruby"
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dp}
### 最小路徑和:動態規劃 ###
def min_path_sum_dp(grid)
n, m = grid.length, grid.first.length
# 初始化 dp 表
dp = Array.new(n) { Array.new(m, 0) }
dp[0][0] = grid[0][0]
# 狀態轉移:首行
(1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }
# 狀態轉移:首列
(1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }
# 狀態轉移:其餘行和列
for i in 1...n
for j in 1...m
dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]
end
end
dp[n -1][m -1]
end
```
=== "Zig"
@ -1521,7 +1562,23 @@ $$
=== "Ruby"
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dp_comp}
### 最小路徑和:空間最佳化後的動態規劃 ###
def min_path_sum_dp_comp(grid)
n, m = grid.length, grid.first.length
# 初始化 dp 表
dp = Array.new(m, 0)
# 狀態轉移:首行
dp[0] = grid[0][0]
(1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }
# 狀態轉移:其餘行
for i in 1...n
# 狀態轉移:首列
dp[0] = dp[0] + grid[i][0]
# 狀態轉移:其餘列
(1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }
end
dp[m - 1]
end
```
=== "Zig"

View File

@ -454,7 +454,27 @@ $$
=== "Ruby"
```ruby title="edit_distance.rb"
[class]{}-[func]{edit_distance_dp}
### 編輯距離:動態規劃 ###
def edit_distance_dp(s, t)
n, m = s.length, t.length
dp = Array.new(n + 1) { Array.new(m + 1, 0) }
# 狀態轉移:首行首列
(1...(n + 1)).each { |i| dp[i][0] = i }
(1...(m + 1)).each { |j| dp[0][j] = j }
# 狀態轉移:其餘行和列
for i in 1...(n + 1)
for j in 1...(m +1)
if s[i - 1] == t[j - 1]
# 若兩字元相等,則直接跳過此兩字元
dp[i][j] = dp[i - 1][j - 1]
else
# 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1
dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1
end
end
end
dp[n][m]
end
```
=== "Zig"
@ -949,7 +969,32 @@ $$
=== "Ruby"
```ruby title="edit_distance.rb"
[class]{}-[func]{edit_distance_dp_comp}
### 編輯距離:空間最佳化後的動態規劃 ###
def edit_distance_dp_comp(s, t)
n, m = s.length, t.length
dp = Array.new(m + 1, 0)
# 狀態轉移:首行
(1...(m + 1)).each { |j| dp[j] = j }
# 狀態轉移:其餘行
for i in 1...(n + 1)
# 狀態轉移:首列
leftup = dp.first # 暫存 dp[i-1, j-1]
dp[0] += 1
# 狀態轉移:其餘列
for j in 1...(m + 1)
temp = dp[j]
if s[i - 1] == t[j - 1]
# 若兩字元相等,則直接跳過此兩字元
dp[j] = leftup
else
# 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1
dp[j] = [dp[j - 1], dp[j], leftup].min + 1
end
leftup = temp # 更新為下一輪的 dp[i-1, j-1]
end
end
dp[m]
end
```
=== "Zig"

View File

@ -395,9 +395,29 @@ comments: true
=== "Ruby"
```ruby title="climbing_stairs_backtrack.rb"
[class]{}-[func]{backtrack}
### 回溯 ###
def backtrack(choices, state, n, res)
# 當爬到第 n 階時,方案數量加 1
res[0] += 1 if state == n
# 走訪所有選擇
for choice in choices
# 剪枝:不允許越過第 n 階
next if state + choice > n
[class]{}-[func]{climbing_stairs_backtrack}
# 嘗試:做出選擇,更新狀態
backtrack(choices, state + choice, n, res)
end
# 回退
end
### 爬樓梯:回溯 ###
def climbing_stairs_backtrack(n)
choices = [1, 2] # 可選擇向上爬 1 階或 2 階
state = 0 # 從第 0 階開始爬
res = [0] # 使用 res[0] 記錄方案數量
backtrack(choices, state, n, res)
res.first
end
```
=== "Zig"
@ -694,9 +714,18 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_dfs.rb"
[class]{}-[func]{dfs}
### 搜尋 ###
def dfs(i)
# 已知 dp[1] 和 dp[2] ,返回之
return i if i == 1 || i == 2
# dp[i] = dp[i-1] + dp[i-2]
dfs(i - 1) + dfs(i - 2)
end
[class]{}-[func]{climbing_stairs_dfs}
### 爬樓梯:搜尋 ###
def climbing_stairs_dfs(n)
dfs(n)
end
```
=== "Zig"
@ -1065,9 +1094,25 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_dfs_mem.rb"
[class]{}-[func]{dfs}
### 記憶化搜尋 ###
def dfs(i, mem)
# 已知 dp[1] 和 dp[2] ,返回之
return i if i == 1 || i == 2
# 若存在記錄 dp[i] ,則直接返回之
return mem[i] if mem[i] != -1
[class]{}-[func]{climbing_stairs_dfs_mem}
# dp[i] = dp[i-1] + dp[i-2]
count = dfs(i - 1, mem) + dfs(i - 2, mem)
# 記錄 dp[i]
mem[i] = count
end
### 爬樓梯:記憶化搜尋 ###
def climbing_stairs_dfs_mem(n)
# mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄
mem = Array.new(n + 1, -1)
dfs(n, mem)
end
```
=== "Zig"
@ -1359,7 +1404,19 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_dp.rb"
[class]{}-[func]{climbing_stairs_dp}
### 爬樓梯:動態規劃 ###
def climbing_stairs_dp(n)
return n if n == 1 || n == 2
# 初始化 dp 表,用於儲存子問題的解
dp = Array.new(n + 1, 0)
# 初始狀態:預設最小子問題的解
dp[1], dp[2] = 1, 2
# 狀態轉移:從較小子問題逐步求解較大子問題
(3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }
dp[n]
end
```
=== "Zig"
@ -1610,7 +1667,15 @@ $$
=== "Ruby"
```ruby title="climbing_stairs_dp.rb"
[class]{}-[func]{climbing_stairs_dp_comp}
### 爬樓梯:空間最佳化後的動態規劃 ###
def climbing_stairs_dp_comp(n)
return n if n == 1 || n == 2
a, b = 1, 2
(3...(n + 1)).each { a, b = b, a + b }
b
end
```
=== "Zig"

View File

@ -324,7 +324,18 @@ $$
=== "Ruby"
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dfs}
### 0-1 背包:暴力搜尋 ###
def knapsack_dfs(wgt, val, i, c)
# 若已選完所有物品或背包無剩餘容量,則返回價值 0
return 0 if i == 0 || c == 0
# 若超過背包容量,則只能選擇不放入背包
return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c
# 計算不放入和放入物品 i 的最大價值
no = knapsack_dfs(wgt, val, i - 1, c)
yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]
# 返回兩種方案中價值更大的那一個
[no, yes].max
end
```
=== "Zig"
@ -700,7 +711,20 @@ $$
=== "Ruby"
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dfs_mem}
### 0-1 背包:記憶化搜尋 ###
def knapsack_dfs_mem(wgt, val, mem, i, c)
# 若已選完所有物品或背包無剩餘容量,則返回價值 0
return 0 if i == 0 || c == 0
# 若已有記錄,則直接返回
return mem[i][c] if mem[i][c] != -1
# 若超過背包容量,則只能選擇不放入背包
return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c
# 計算不放入和放入物品 i 的最大價值
no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)
yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]
# 記錄並返回兩種方案中價值更大的那一個
mem[i][c] = [no, yes].max
end
```
=== "Zig"
@ -1059,7 +1083,25 @@ $$
=== "Ruby"
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dp}
### 0-1 背包:動態規劃 ###
def knapsack_dp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# 狀態轉移
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# 若超過背包容量,則不選物品 i
dp[i][c] = dp[i - 1][c]
else
# 不選和選物品 i 這兩種方案的較大值
dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[n][cap]
end
```
=== "Zig"
@ -1446,7 +1488,26 @@ $$
=== "Ruby"
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dp_comp}
### 0-1 背包:空間最佳化後的動態規劃 ###
def knapsack_dp_comp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(cap + 1, 0)
# 狀態轉移
for i in 1...(n + 1)
# 倒序走訪
for c in cap.downto(1)
if wgt[i - 1] > c
# 若超過背包容量,則不選物品 i
dp[c] = dp[c]
else
# 不選和選物品 i 這兩種方案的較大值
dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[cap]
end
```
=== "Zig"

View File

@ -350,7 +350,25 @@ $$
=== "Ruby"
```ruby title="unbounded_knapsack.rb"
[class]{}-[func]{unbounded_knapsack_dp}
### 完全背包:動態規劃 ###
def unbounded_knapsack_dp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# 狀態轉移
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# 若超過背包容量,則不選物品 i
dp[i][c] = dp[i - 1][c]
else
# 不選和選物品 i 這兩種方案的較大值
dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[n][cap]
end
```
=== "Zig"
@ -709,7 +727,46 @@ $$
=== "Ruby"
```ruby title="unbounded_knapsack.rb"
[class]{}-[func]{unbounded_knapsack_dp_comp}
### 完全背包:動態規劃 ###
def unbounded_knapsack_dp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# 狀態轉移
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# 若超過背包容量,則不選物品 i
dp[i][c] = dp[i - 1][c]
else
# 不選和選物品 i 這兩種方案的較大值
dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[n][cap]
end
### 完全背包:空間最佳化後的動態規劃 ##3
def unbounded_knapsack_dp_comp(wgt, val, cap)
n = wgt.length
# 初始化 dp 表
dp = Array.new(cap + 1, 0)
# 狀態轉移
for i in 1...(n + 1)
# 正序走訪
for c in 1...(cap + 1)
if wgt[i -1] > c
# 若超過背包容量,則不選物品 i
dp[c] = dp[c]
else
# 不選和選物品 i 這兩種方案的較大值
dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[cap]
end
```
=== "Zig"
@ -1159,7 +1216,28 @@ $$
=== "Ruby"
```ruby title="coin_change.rb"
[class]{}-[func]{coin_change_dp}
### 零錢兌換:動態規劃 ###
def coin_change_dp(coins, amt)
n = coins.length
_MAX = amt + 1
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
# 狀態轉移:首行首列
(1...(amt + 1)).each { |a| dp[0][a] = _MAX }
# 狀態轉移:其餘行和列
for i in 1...(n + 1)
for a in 1...(amt + 1)
if coins[i - 1] > a
# 若超過目標金額,則不選硬幣 i
dp[i][a] = dp[i - 1][a]
else
# 不選和選硬幣 i 這兩種方案的較小值
dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min
end
end
end
dp[n][amt] != _MAX ? dp[n][amt] : -1
end
```
=== "Zig"
@ -1586,7 +1664,28 @@ $$
=== "Ruby"
```ruby title="coin_change.rb"
[class]{}-[func]{coin_change_dp_comp}
### 零錢兌換:空間最佳化後的動態規劃 ###
def coin_change_dp_comp(coins, amt)
n = coins.length
_MAX = amt + 1
# 初始化 dp 表
dp = Array.new(amt + 1, _MAX)
dp[0] = 0
# 狀態轉移
for i in 1...(n + 1)
# 正序走訪
for a in 1...(amt + 1)
if coins[i - 1] > a
# 若超過目標金額,則不選硬幣 i
dp[a] = dp[a]
else
# 不選和選硬幣 i 這兩種方案的較小值
dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min
end
end
end
dp[amt] != _MAX ? dp[amt] : -1
end
```
=== "Zig"
@ -1999,7 +2098,27 @@ $$
=== "Ruby"
```ruby title="coin_change_ii.rb"
[class]{}-[func]{coin_change_ii_dp}
### 零錢兌換 II動態規劃 ###
def coin_change_ii_dp(coins, amt)
n = coins.length
# 初始化 dp 表
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
# 初始化首列
(0...(n + 1)).each { |i| dp[i][0] = 1 }
# 狀態轉移
for i in 1...(n + 1)
for a in 1...(amt + 1)
if coins[i - 1] > a
# 若超過目標金額,則不選硬幣 i
dp[i][a] = dp[i - 1][a]
else
# 不選和選硬幣 i 這兩種方案之和
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]
end
end
end
dp[n][amt]
end
```
=== "Zig"
@ -2343,7 +2462,27 @@ $$
=== "Ruby"
```ruby title="coin_change_ii.rb"
[class]{}-[func]{coin_change_ii_dp_comp}
### 零錢兌換 II空間最佳化後的動態規劃 ###
def coin_change_ii_dp_comp(coins, amt)
n = coins.length
# 初始化 dp 表
dp = Array.new(amt + 1, 0)
dp[0] = 1
# 狀態轉移
for i in 1...(n + 1)
# 正序走訪
for a in 1...(amt + 1)
if coins[i - 1] > a
# 若超過目標金額,則不選硬幣 i
dp[a] = dp[a]
else
# 不選和選硬幣 i 這兩種方案之和
dp[a] = dp[a] + dp[a - coins[i - 1]]
end
end
end
dp[amt]
end
```
=== "Zig"

View File

@ -311,7 +311,15 @@ comments: true
=== "Ruby"
```ruby title="my_heap.rb"
[class]{MaxHeap}-[func]{__init__}
### 建構子,根據輸入串列建堆積 ###
def initialize(nums)
# 將串列元素原封不動新增進堆積
@max_heap = nums
# 堆積化除葉節點以外的其他所有節點
parent(size - 1).downto(0) do |i|
sift_down(i)
end
end
```
=== "Zig"

View File

@ -132,17 +132,17 @@ comments: true
Queue<Integer> minHeap = new PriorityQueue<>();
// 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可)
Queue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
/* 元素入堆積 */
maxHeap.offer(1);
maxHeap.offer(3);
maxHeap.offer(2);
maxHeap.offer(5);
maxHeap.offer(4);
/* 獲取堆積頂元素 */
int peek = maxHeap.peek(); // 5
/* 堆積頂元素出堆積 */
// 出堆積元素會形成一個從大到小的序列
peek = maxHeap.poll(); // 5
@ -150,13 +150,13 @@ comments: true
peek = maxHeap.poll(); // 3
peek = maxHeap.poll(); // 2
peek = maxHeap.poll(); // 1
/* 獲取堆積大小 */
int size = maxHeap.size();
/* 判斷堆積是否為空 */
boolean isEmpty = maxHeap.isEmpty();
/* 輸入串列並建堆積 */
minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));
```
@ -347,7 +347,7 @@ comments: true
max_heap.push(2);
max_heap.push(5);
max_heap.push(4);
/* 獲取堆積頂元素 */
let peek = max_heap.peek().unwrap(); // 5
@ -383,17 +383,17 @@ comments: true
var minHeap = PriorityQueue<Int>()
// 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可)
val maxHeap = PriorityQueue { a: Int, b: Int -> b - a }
/* 元素入堆積 */
maxHeap.offer(1)
maxHeap.offer(3)
maxHeap.offer(2)
maxHeap.offer(5)
maxHeap.offer(4)
/* 獲取堆積頂元素 */
var peek = maxHeap.peek() // 5
/* 堆積頂元素出堆積 */
// 出堆積元素會形成一個從大到小的序列
peek = maxHeap.poll() // 5
@ -401,13 +401,13 @@ comments: true
peek = maxHeap.poll() // 3
peek = maxHeap.poll() // 2
peek = maxHeap.poll() // 1
/* 獲取堆積大小 */
val size = maxHeap.size
/* 判斷堆積是否為空 */
val isEmpty = maxHeap.isEmpty()
/* 輸入串列並建堆積 */
minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))
```
@ -415,7 +415,7 @@ comments: true
=== "Ruby"
```ruby title="heap.rb"
# Ruby 未提供內建 Heap 類別
```
=== "Zig"