From 1184c791c54af43c7422a46399d81e7300de29aa Mon Sep 17 00:00:00 2001 From: krahets Date: Wed, 19 Jul 2023 01:44:39 +0800 Subject: [PATCH] build --- .../backtracking_algorithm.md | 2 +- .../binary_search_recur.md | 27 +++- .../build_binary_tree_problem.md | 28 +++- chapter_divide_and_conquer/hanota_problem.md | 33 ++++- chapter_divide_and_conquer/summary.md | 2 +- .../dp_problem_features.md | 51 ++++++- .../dp_solution_pipeline.md | 86 ++++++++++- .../edit_distance_problem.md | 57 +++++++- .../intro_to_dynamic_programming.md | 96 +++++++++++-- .../knapsack_problem.md | 77 +++++++++- .../unbounded_knapsack_problem.md | 134 +++++++++++++++++- chapter_graph/graph_traversal.md | 7 +- chapter_heap/build_heap.md | 2 +- chapter_tree/binary_tree_traversal.md | 3 +- 14 files changed, 563 insertions(+), 42 deletions(-) diff --git a/chapter_backtracking/backtracking_algorithm.md b/chapter_backtracking/backtracking_algorithm.md index 2370c0a76..c9b02632e 100644 --- a/chapter_backtracking/backtracking_algorithm.md +++ b/chapter_backtracking/backtracking_algorithm.md @@ -1364,7 +1364,7 @@ comments: true - 上文介绍过的剪枝是一种常用的优化方法。它可以避免搜索那些肯定不会产生有效解的路径,从而节省时间和空间。 - 另一个常用的优化方法是加入「启发式搜索 Heuristic Search」策略,它在搜索过程中引入一些策略或者估计值,从而优先搜索最有可能产生有效解的路径。 -## 13.1.6.   典型例题 +## 13.1.6.   回溯典型例题 **搜索问题**:这类问题的目标是找到满足特定条件的解决方案。 diff --git a/chapter_divide_and_conquer/binary_search_recur.md b/chapter_divide_and_conquer/binary_search_recur.md index 6033e7172..6fd5e9910 100644 --- a/chapter_divide_and_conquer/binary_search_recur.md +++ b/chapter_divide_and_conquer/binary_search_recur.md @@ -160,9 +160,32 @@ status: new === "C#" ```csharp title="binary_search_recur.cs" - [class]{binary_search_recur}-[func]{dfs} + /* 二分查找:问题 f(i, j) */ + int dfs(int[] nums, int target, int i, int j) { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1; + } + // 计算中点索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目标元素,返回其索引 + return m; + } + } - [class]{binary_search_recur}-[func]{binarySearch} + /* 二分查找 */ + int binarySearch(int[] nums, int target) { + int n = nums.Length; + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); + } ``` === "Swift" diff --git a/chapter_divide_and_conquer/build_binary_tree_problem.md b/chapter_divide_and_conquer/build_binary_tree_problem.md index 41cc4ae4a..f4b2bd50a 100644 --- a/chapter_divide_and_conquer/build_binary_tree_problem.md +++ b/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -195,9 +195,33 @@ status: new === "C#" ```csharp title="build_tree.cs" - [class]{build_tree}-[func]{dfs} + /* 构建二叉树:分治 */ + TreeNode dfs(int[] preorder, int[] inorder, Dictionary hmap, int i, int l, int r) { + // 子树区间为空时终止 + if (r - l < 0) + return null; + // 初始化根节点 + TreeNode root = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = hmap[preorder[i]]; + // 子问题:构建左子树 + root.left = dfs(preorder, inorder, hmap, i + 1, l, m - 1); + // 子问题:构建右子树 + root.right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; + } - [class]{build_tree}-[func]{buildTree} + /* 构建二叉树 */ + TreeNode buildTree(int[] preorder, int[] inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + Dictionary hmap = new Dictionary(); + for (int i = 0; i < inorder.Length; i++) { + hmap.TryAdd(inorder[i], i); + } + TreeNode root = dfs(preorder, inorder, hmap, 0, 0, inorder.Length - 1); + return root; + } ``` === "Swift" diff --git a/chapter_divide_and_conquer/hanota_problem.md b/chapter_divide_and_conquer/hanota_problem.md index df2a0afdc..4f187a934 100644 --- a/chapter_divide_and_conquer/hanota_problem.md +++ b/chapter_divide_and_conquer/hanota_problem.md @@ -112,7 +112,7 @@ status: new } /* 求解汉诺塔 */ - void hanota(List A, List B, List C) { + void solveHanota(List A, List B, List C) { int n = A.size(); // 将 A 顶部 n 个圆盘借助 B 移到 C dfs(n, A, B, C); @@ -227,11 +227,36 @@ status: new === "C#" ```csharp title="hanota.cs" - [class]{hanota}-[func]{move} + /* 移动一个圆盘 */ + void move(List src, List tar) { + // 从 src 顶部拿出一个圆盘 + int pan = src[^1]; + src.RemoveAt(src.Count - 1); + // 将圆盘放入 tar 顶部 + tar.Add(pan); + } - [class]{hanota}-[func]{dfs} + /* 求解汉诺塔:问题 f(i) */ + void dfs(int i, List src, List buf, List tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); + } - [class]{hanota}-[func]{hanota} + /* 求解汉诺塔 */ + void solveHanota(List A, List B, List C) { + int n = A.Count; + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); + } ``` === "Swift" diff --git a/chapter_divide_and_conquer/summary.md b/chapter_divide_and_conquer/summary.md index 6edb18c43..9ed78a801 100644 --- a/chapter_divide_and_conquer/summary.md +++ b/chapter_divide_and_conquer/summary.md @@ -11,6 +11,6 @@ status: new - 引入分治策略往往可以带来算法效率的提升。一方面,分治策略减少了计算吧操作数量;另一方面,分治后有利于系统的并行优化。 - 分治既可以解决许多算法问题,也广泛应用于数据结构与算法设计中,处处可见其身影。 - 相较于暴力搜索,自适应搜索效率更高。时间复杂度为 $O(\log n)$ 的搜索算法通常都是基于分治策略实现的。 -- 二分查找也体现了分治思想,我们可以通过递归分治实现二分查找。 +- 二分查找是分治思想的另一个典型应用,它不包含将子问题的解进行合并的步骤。我们可以通过递归分治实现二分查找。 - 在构建二叉树问题中,构建树(原问题)可以被划分为构建左子树和右子树(子问题),其可以通过划分前序遍历和中序遍历的索引区间来实现。 - 在汉诺塔问题中,一个规模为 $n$ 的问题可以被划分为两个规模为 $n-1$ 的子问题和一个规模为 $1$ 的子问题。按顺序解决这三个子问题后,原问题随之得到解决。 diff --git a/chapter_dynamic_programming/dp_problem_features.md b/chapter_dynamic_programming/dp_problem_features.md index 8e08d6225..5a9e3255a 100644 --- a/chapter_dynamic_programming/dp_problem_features.md +++ b/chapter_dynamic_programming/dp_problem_features.md @@ -143,7 +143,23 @@ $$ === "Swift" ```swift title="min_cost_climbing_stairs_dp.swift" - [class]{}-[func]{minCostClimbingStairsDP} + /* 爬楼梯最小代价:动态规划 */ + func minCostClimbingStairsDP(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i in stride(from: 3, through: n, by: 1) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] + } ``` === "Zig" @@ -275,7 +291,18 @@ $$ === "Swift" ```swift title="min_cost_climbing_stairs_dp.swift" - [class]{}-[func]{minCostClimbingStairsDPComp} + /* 爬楼梯最小代价:状态压缩后的动态规划 */ + func minCostClimbingStairsDPComp(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + var (a, b) = (cost[1], cost[2]) + for i in stride(from: 3, through: n, by: 1) { + (a, b) = (b, min(a, b) + cost[i]) + } + return b + } ``` === "Zig" @@ -465,7 +492,25 @@ $$ === "Swift" ```swift title="climbing_stairs_constraint_dp.swift" - [class]{}-[func]{climbingStairsConstraintDP} + /* 带约束爬楼梯:动态规划 */ + func climbingStairsConstraintDP(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状态转移:从较小子问题逐步求解较大子问题 + for i in stride(from: 3, through: n, by: 1) { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] + } ``` === "Zig" diff --git a/chapter_dynamic_programming/dp_solution_pipeline.md b/chapter_dynamic_programming/dp_solution_pipeline.md index 4a359c71a..8591dddef 100644 --- a/chapter_dynamic_programming/dp_solution_pipeline.md +++ b/chapter_dynamic_programming/dp_solution_pipeline.md @@ -216,7 +216,22 @@ $$ === "Swift" ```swift title="min_path_sum.swift" - [class]{}-[func]{minPathSumDFS} + /* 最小路径和:暴力搜索 */ + func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { + // 若为左上角单元格,则终止搜索 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return .max + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + let left = minPathSumDFS(grid: grid, i: i - 1, j: j) + let up = minPathSumDFS(grid: grid, i: i, j: j - 1) + // 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) + grid[i][j] + } ``` === "Zig" @@ -389,7 +404,27 @@ $$ === "Swift" ```swift title="min_path_sum.swift" - [class]{}-[func]{minPathSumDFSMem} + /* 最小路径和:记忆化搜索 */ + func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若为左上角单元格,则终止搜索 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return .max + } + // 若已有记录,则直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左边和上边单元格的最小路径代价 + let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) + let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] + } ``` === "Zig" @@ -565,7 +600,29 @@ $$ === "Swift" ```swift title="min_path_sum.swift" - [class]{}-[func]{minPathSumDP} + /* 最小路径和:动态规划 */ + func minPathSumDP(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: m), count: n) + dp[0][0] = grid[0][0] + // 状态转移:首行 + for j in stride(from: 1, to: m, by: 1) { + dp[0][j] = dp[0][j - 1] + grid[0][j] + } + // 状态转移:首列 + for i in stride(from: 1, to: n, by: 1) { + dp[i][0] = dp[i - 1][0] + grid[i][0] + } + // 状态转移:其余行列 + for i in stride(from: 1, to: n, by: 1) { + for j in stride(from: 1, to: m, by: 1) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + } + } + return dp[n - 1][m - 1] + } ``` === "Zig" @@ -771,7 +828,28 @@ $$ === "Swift" ```swift title="min_path_sum.swift" - [class]{}-[func]{minPathSumDPComp} + /* 最小路径和:状态压缩后的动态规划 */ + func minPathSumDPComp(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: m) + // 状态转移:首行 + dp[0] = grid[0][0] + for j in stride(from: 1, to: m, by: 1) { + dp[j] = dp[j - 1] + grid[0][j] + } + // 状态转移:其余行 + for i in stride(from: 1, to: n, by: 1) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + // 状态转移:其余列 + for j in stride(from: 1, to: m, by: 1) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + } + } + return dp[m - 1] + } ``` === "Zig" diff --git a/chapter_dynamic_programming/edit_distance_problem.md b/chapter_dynamic_programming/edit_distance_problem.md index 4fa68c331..ae7f67b37 100644 --- a/chapter_dynamic_programming/edit_distance_problem.md +++ b/chapter_dynamic_programming/edit_distance_problem.md @@ -213,7 +213,32 @@ $$ === "Swift" ```swift title="edit_distance.swift" - [class]{}-[func]{editDistanceDP} + /* 编辑距离:动态规划 */ + func editDistanceDP(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) + // 状态转移:首行首列 + for i in stride(from: 1, through: n, by: 1) { + dp[i][0] = i + } + for j in stride(from: 1, through: m, by: 1) { + dp[0][j] = j + } + // 状态转移:其余行列 + for i in stride(from: 1, through: n, by: 1) { + for j in stride(from: 1, through: m, by: 1) { + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] + } ``` === "Zig" @@ -458,7 +483,35 @@ $$ === "Swift" ```swift title="edit_distance.swift" - [class]{}-[func]{editDistanceDPComp} + /* 编辑距离:状态压缩后的动态规划 */ + func editDistanceDPComp(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: 0, count: m + 1) + // 状态转移:首行 + for j in stride(from: 1, through: m, by: 1) { + dp[j] = j + } + // 状态转移:其余行 + for i in stride(from: 1, through: n, by: 1) { + // 状态转移:首列 + var leftup = dp[0] // 暂存 dp[i-1, j-1] + dp[0] = i + // 状态转移:其余列 + for j in stride(from: 1, through: m, by: 1) { + let temp = dp[j] + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m] + } ``` === "Zig" diff --git a/chapter_dynamic_programming/intro_to_dynamic_programming.md b/chapter_dynamic_programming/intro_to_dynamic_programming.md index f47a89e62..a675d526a 100644 --- a/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -170,9 +170,31 @@ status: new === "Swift" ```swift title="climbing_stairs_backtrack.swift" - [class]{}-[func]{backtrack} + /* 回溯 */ + func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { + // 当爬到第 n 阶时,方案数量加 1 + if state == n { + res[0] += 1 + } + // 遍历所有选择 + for choice in choices { + // 剪枝:不允许越过第 n 阶 + if state + choice > n { + break + } + backtrack(choices: choices, state: state + choice, n: n, res: &res) + } + } - [class]{}-[func]{climbingStairsBacktrack} + /* 爬楼梯:回溯 */ + func climbingStairsBacktrack(n: Int) -> Int { + let choices = [1, 2] // 可选择向上爬 1 或 2 阶 + let state = 0 // 从第 0 阶开始爬 + var res: [Int] = [] + res.append(0) // 使用 res[0] 记录方案数量 + backtrack(choices: choices, state: state, n: n, res: &res) + return res[0] + } ``` === "Zig" @@ -353,9 +375,21 @@ $$ === "Swift" ```swift title="climbing_stairs_dfs.swift" - [class]{}-[func]{dfs} + /* 搜索 */ + func dfs(i: Int) -> Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1) + dfs(i: i - 2) + return count + } - [class]{}-[func]{climbingStairsDFS} + /* 爬楼梯:搜索 */ + func climbingStairsDFS(n: Int) -> Int { + dfs(i: n) + } ``` === "Zig" @@ -540,9 +574,29 @@ $$ === "Swift" ```swift title="climbing_stairs_dfs_mem.swift" - [class]{}-[func]{dfs} + /* 记忆化搜索 */ + func dfs(i: Int, mem: inout [Int]) -> Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // 若存在记录 dp[i] ,则直接返回之 + if mem[i] != -1 { + return mem[i] + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) + // 记录 dp[i] + mem[i] = count + return count + } - [class]{}-[func]{climbingStairsDFSMem} + /* 爬楼梯:记忆化搜索 */ + func climbingStairsDFSMem(n: Int) -> Int { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + var mem = Array(repeating: -1, count: n + 1) + return dfs(i: n, mem: &mem) + } ``` === "Zig" @@ -699,7 +753,22 @@ $$ === "Swift" ```swift title="climbing_stairs_dp.swift" - [class]{}-[func]{climbingStairsDP} + /* 爬楼梯:动态规划 */ + func climbingStairsDP(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i in stride(from: 3, through: n, by: 1) { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] + } ``` === "Zig" @@ -833,7 +902,18 @@ $$ === "Swift" ```swift title="climbing_stairs_dp.swift" - [class]{}-[func]{climbingStairsDPComp} + /* 爬楼梯:状态压缩后的动态规划 */ + func climbingStairsDPComp(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + var a = 1 + var b = 2 + for _ in stride(from: 3, through: n, by: 1) { + (a, b) = (b, a + b) + } + return b + } ``` === "Zig" diff --git a/chapter_dynamic_programming/knapsack_problem.md b/chapter_dynamic_programming/knapsack_problem.md index 5552a5794..e89e1f1f6 100644 --- a/chapter_dynamic_programming/knapsack_problem.md +++ b/chapter_dynamic_programming/knapsack_problem.md @@ -172,7 +172,22 @@ $$ === "Swift" ```swift title="knapsack.swift" - [class]{}-[func]{knapsackDFS} + /* 0-1 背包:暴力搜索 */ + func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { + // 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超过背包容量,则只能不放入背包 + if wgt[i - 1] > c { + return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + } + // 计算不放入和放入物品 i 的最大价值 + let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 返回两种方案中价值更大的那一个 + return max(no, yes) + } ``` === "Zig" @@ -343,7 +358,27 @@ $$ === "Swift" ```swift title="knapsack.swift" - [class]{}-[func]{knapsackDFSMem} + /* 0-1 背包:记忆化搜索 */ + func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { + // 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 || c == 0 { + return 0 + } + // 若已有记录,则直接返回 + if mem[i][c] != -1 { + return mem[i][c] + } + // 若超过背包容量,则只能不放入背包 + if wgt[i - 1] > c { + return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + } + // 计算不放入和放入物品 i 的最大价值 + let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes) + return mem[i][c] + } ``` === "Zig" @@ -507,7 +542,25 @@ $$ === "Swift" ```swift title="knapsack.swift" - [class]{}-[func]{knapsackDP} + /* 0-1 背包:动态规划 */ + func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for c in stride(from: 1, through: cap, by: 1) { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] + } ``` === "Zig" @@ -727,7 +780,23 @@ $$ === "Swift" ```swift title="knapsack.swift" - [class]{}-[func]{knapsackDPComp} + /* 0-1 背包:状态压缩后的动态规划 */ + func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + // 倒序遍历 + for c in stride(from: cap, through: 1, by: -1) { + if wgt[i - 1] <= c { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] + } ``` === "Zig" diff --git a/chapter_dynamic_programming/unbounded_knapsack_problem.md b/chapter_dynamic_programming/unbounded_knapsack_problem.md index 882a5282a..9fe60659e 100644 --- a/chapter_dynamic_programming/unbounded_knapsack_problem.md +++ b/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -154,7 +154,25 @@ $$ === "Swift" ```swift title="unbounded_knapsack.swift" - [class]{}-[func]{unboundedKnapsackDP} + /* 完全背包:动态规划 */ + func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for c in stride(from: 1, through: cap, by: 1) { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] + } ``` === "Zig" @@ -329,7 +347,25 @@ $$ === "Swift" ```swift title="unbounded_knapsack.swift" - [class]{}-[func]{unboundedKnapsackDPComp} + /* 完全背包:状态压缩后的动态规划 */ + func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for c in stride(from: 1, through: cap, by: 1) { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] + } ``` === "Zig" @@ -368,7 +404,7 @@ $$ !!! question - 给定 $n$ 种硬币,第 $i$ 个硬币的面值为 $coins[i - 1]$ ,为目标金额 $amt$ ,**每种硬币可以重复选取**,问能够凑出目标金额的最少硬币个数。如果无法凑出目标金额则返回 $-1$ 。 + 给定 $n$ 种硬币,第 $i$ 个硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,**每种硬币可以重复选取**,问能够凑出目标金额的最少硬币个数。如果无法凑出目标金额则返回 $-1$ 。 如下图所示,凑出 $11$ 元最少需要 $3$ 枚硬币,方案为 $1 + 2 + 5 = 11$ 。 @@ -547,7 +583,30 @@ $$ === "Swift" ```swift title="coin_change.swift" - [class]{}-[func]{coinChangeDP} + /* 零钱兑换:动态规划 */ + func coinChangeDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 状态转移:首行首列 + for a in stride(from: 1, through: amt, by: 1) { + dp[0][a] = MAX + } + // 状态转移:其余行列 + for i in stride(from: 1, through: n, by: 1) { + for a in stride(from: 1, through: amt, by: 1) { + if coins[i - 1] > a { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1 + } ``` === "Zig" @@ -768,7 +827,27 @@ $$ === "Swift" ```swift title="coin_change.swift" - [class]{}-[func]{coinChangeDPComp} + /* 零钱兑换:状态压缩后的动态规划 */ + func coinChangeDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: MAX, count: amt + 1) + dp[0] = 0 + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for a in stride(from: 1, through: amt, by: 1) { + if coins[i - 1] > a { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return dp[amt] != MAX ? dp[amt] : -1 + } ``` === "Zig" @@ -962,7 +1041,29 @@ $$ === "Swift" ```swift title="coin_change_ii.swift" - [class]{}-[func]{coinChangeIIDP} + /* 零钱兑换 II:动态规划 */ + func coinChangeIIDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 初始化首列 + for i in stride(from: 0, through: n, by: 1) { + dp[i][0] = 1 + } + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for a in stride(from: 1, through: amt, by: 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]] + } + } + } + return dp[n][amt] + } ``` === "Zig" @@ -1125,7 +1226,26 @@ $$ === "Swift" ```swift title="coin_change_ii.swift" - [class]{}-[func]{coinChangeIIDPComp} + /* 零钱兑换 II:状态压缩后的动态规划 */ + func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: amt + 1) + dp[0] = 1 + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for a in stride(from: 1, through: amt, by: 1) { + if coins[i - 1] > a { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] + } ``` === "Zig" diff --git a/chapter_graph/graph_traversal.md b/chapter_graph/graph_traversal.md index 7e6c0e2c1..33a5f1295 100644 --- a/chapter_graph/graph_traversal.md +++ b/chapter_graph/graph_traversal.md @@ -39,9 +39,11 @@ BFS 通常借助「队列」来实现。队列具有“先入先出”的性质 // 顶点遍历序列 List res = new ArrayList<>(); // 哈希表,用于记录已被访问过的顶点 - Set visited = new HashSet<>() {{ add(startVet); }}; + Set visited = new HashSet<>(); + visited.add(startVet); // 队列用于实现 BFS - Queue que = new LinkedList<>() {{ offer(startVet); }}; + Queue que = new LinkedList<>(); + que.offer(startVet); // 以顶点 vet 为起点,循环直至访问完所有顶点 while (!que.isEmpty()) { Vertex vet = que.poll(); // 队首顶点出队 @@ -448,6 +450,7 @@ BFS 通常借助「队列」来实现。队列具有“先入先出”的性质 def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: """深度优先遍历 DFS""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 # 顶点遍历序列 res = [] # 哈希表,用于记录已被访问过的顶点 diff --git a/chapter_heap/build_heap.md b/chapter_heap/build_heap.md index e38a3fe4a..48aef4068 100644 --- a/chapter_heap/build_heap.md +++ b/chapter_heap/build_heap.md @@ -217,7 +217,7 @@ $$ $$ \begin{aligned} T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline -& = 2^{h+1} - h \newline +& = 2^{h+1} - h - 2 \newline & = O(2^h) \end{aligned} $$ diff --git a/chapter_tree/binary_tree_traversal.md b/chapter_tree/binary_tree_traversal.md index 98926e71c..06bafce22 100755 --- a/chapter_tree/binary_tree_traversal.md +++ b/chapter_tree/binary_tree_traversal.md @@ -28,7 +28,8 @@ comments: true /* 层序遍历 */ List levelOrder(TreeNode root) { // 初始化队列,加入根节点 - Queue queue = new LinkedList<>() {{ add(root); }}; + Queue queue = new LinkedList<>(); + queue.add(root); // 初始化一个列表,用于保存遍历序列 List list = new ArrayList<>(); while (!queue.isEmpty()) {