From 9ea8a73059c07a938d164e87234d69453c2e3a3a Mon Sep 17 00:00:00 2001 From: nuomi1 Date: Tue, 18 Jul 2023 12:49:03 +0800 Subject: [PATCH] Feature/chapter dynamic programming swift (#608) * feat: add Swift codes for intro_to_dynamic_programming article * feat: add Swift codes for dp_problem_features article * feat: add Swift codes for dp_solution_pipeline article * feat: add Swift codes for knapsack_problem article * feat: add Swift codes for unbounded_knapsack_problem article * feat: add Swift codes for edit_distance_problem article --- codes/swift/Package.swift | 26 ++++ .../climbing_stairs_backtrack.swift | 42 +++++ .../climbing_stairs_constraint_dp.swift | 36 +++++ .../climbing_stairs_dfs.swift | 32 ++++ .../climbing_stairs_dfs_mem.swift | 40 +++++ .../climbing_stairs_dp.swift | 49 ++++++ .../coin_change.swift | 69 ++++++++ .../coin_change_ii.swift | 67 ++++++++ .../edit_distance.swift | 147 ++++++++++++++++++ .../knapsack.swift | 110 +++++++++++++ .../min_cost_climbing_stairs_dp.swift | 51 ++++++ .../min_path_sum.swift | 123 +++++++++++++++ .../unbounded_knapsack.swift | 63 ++++++++ 13 files changed, 855 insertions(+) create mode 100644 codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift create mode 100644 codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift create mode 100644 codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift create mode 100644 codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift create mode 100644 codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift create mode 100644 codes/swift/chapter_dynamic_programming/coin_change.swift create mode 100644 codes/swift/chapter_dynamic_programming/coin_change_ii.swift create mode 100644 codes/swift/chapter_dynamic_programming/edit_distance.swift create mode 100644 codes/swift/chapter_dynamic_programming/knapsack.swift create mode 100644 codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift create mode 100644 codes/swift/chapter_dynamic_programming/min_path_sum.swift create mode 100644 codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift diff --git a/codes/swift/Package.swift b/codes/swift/Package.swift index 19a8ae473..7ef714a93 100644 --- a/codes/swift/Package.swift +++ b/codes/swift/Package.swift @@ -72,6 +72,19 @@ let package = Package( .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), .executable(name: "n_queens", targets: ["n_queens"]), + // chapter_dynamic_programming + .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), + .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), + .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), + .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), + .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), + .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), + .executable(name: "min_path_sum", targets: ["min_path_sum"]), + .executable(name: "knapsack", targets: ["knapsack"]), + .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), + .executable(name: "coin_change", targets: ["coin_change"]), + .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), + .executable(name: "edit_distance", targets: ["edit_distance"]), ], targets: [ // helper @@ -144,5 +157,18 @@ let package = Package( .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), + // chapter_dynamic_programming + .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), + .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), + .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), + .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), + .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), + .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), + .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), + .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), + .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), + .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), + .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), + .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), ] ) diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift new file mode 100644 index 000000000..69469972e --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift @@ -0,0 +1,42 @@ +/** + * File: climbing_stairs_backtrack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯 */ +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) + } +} + +/* 爬楼梯:回溯 */ +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] +} + +@main +enum ClimbingStairsBacktrack { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsBacktrack(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift new file mode 100644 index 000000000..c4a879f4b --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 带约束爬楼梯:动态规划 */ +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] +} + +@main +enum ClimbingStairsConstraintDP { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsConstraintDP(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift new file mode 100644 index 000000000..1b1d3ffc9 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 搜索 */ +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 +} + +/* 爬楼梯:搜索 */ +func climbingStairsDFS(n: Int) -> Int { + dfs(i: n) +} + +@main +enum ClimbingStairsDFS { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFS(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift new file mode 100644 index 000000000..af9797fbc --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dfs_mem.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 记忆化搜索 */ +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 +} + +/* 爬楼梯:记忆化搜索 */ +func climbingStairsDFSMem(n: Int) -> Int { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + var mem = Array(repeating: -1, count: n + 1) + return dfs(i: n, mem: &mem) +} + +@main +enum ClimbingStairsDFSMem { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFSMem(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift new file mode 100644 index 000000000..8c7a93f87 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 爬楼梯:动态规划 */ +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] +} + +/* 爬楼梯:状态压缩后的动态规划 */ +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 +} + +@main +enum ClimbingStairsDP { + /* Driver Code */ + static func main() { + let n = 9 + + var res = climbingStairsDP(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + + res = climbingStairsDPComp(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/coin_change.swift b/codes/swift/chapter_dynamic_programming/coin_change.swift new file mode 100644 index 000000000..0aadc9cf0 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/coin_change.swift @@ -0,0 +1,69 @@ +/** + * File: coin_change.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零钱兑换:动态规划 */ +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 +} + +/* 零钱兑换:状态压缩后的动态规划 */ +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 +} + +@main +enum CoinChange { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 4 + + // 动态规划 + var res = coinChangeDP(coins: coins, amt: amt) + print("凑到目标金额所需的最少硬币数量为 \(res)") + + // 状态压缩后的动态规划 + res = coinChangeDPComp(coins: coins, amt: amt) + print("凑到目标金额所需的最少硬币数量为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/coin_change_ii.swift b/codes/swift/chapter_dynamic_programming/coin_change_ii.swift new file mode 100644 index 000000000..2255e9a44 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/coin_change_ii.swift @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零钱兑换 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] +} + +/* 零钱兑换 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] +} + +@main +enum CoinChangeII { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 5 + + // 动态规划 + var res = coinChangeIIDP(coins: coins, amt: amt) + print("凑出目标金额的硬币组合数量为 \(res)") + + // 状态压缩后的动态规划 + res = coinChangeIIDPComp(coins: coins, amt: amt) + print("凑出目标金额的硬币组合数量为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/edit_distance.swift b/codes/swift/chapter_dynamic_programming/edit_distance.swift new file mode 100644 index 000000000..64b15c901 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/edit_distance.swift @@ -0,0 +1,147 @@ +/** + * File: edit_distance.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 编辑距离:暴力搜索 */ +func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { + // 若 s 和 t 都为空,则返回 0 + if i == 0, j == 0 { + return 0 + } + // 若 s 为空,则返回 t 长度 + if i == 0 { + return j + } + // 若 t 为空,则返回 s 长度 + if j == 0 { + return i + } + // 若两字符相等,则直接跳过此两字符 + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 返回最少编辑步数 + return min(min(insert, delete), replace) + 1 +} + +/* 编辑距离:记忆化搜索 */ +func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若 s 和 t 都为空,则返回 0 + if i == 0, j == 0 { + return 0 + } + // 若 s 为空,则返回 t 长度 + if i == 0 { + return j + } + // 若 t 为空,则返回 s 长度 + if j == 0 { + return i + } + // 若已有记录,则直接返回之 + if mem[i][j] != -1 { + return mem[i][j] + } + // 若两字符相等,则直接跳过此两字符 + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 记录并返回最少编辑步数 + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* 编辑距离:动态规划 */ +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] +} + +/* 编辑距离:状态压缩后的动态规划 */ +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] +} + +@main +enum EditDistance { + /* Driver Code */ + static func main() { + let s = "bag" + let t = "pack" + let n = s.utf8CString.count + let m = t.utf8CString.count + + // 暴力搜索 + var res = editDistanceDFS(s: s, t: t, i: n, j: m) + print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") + + // 记忆化搜索 + var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) + res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) + print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") + + // 动态规划 + res = editDistanceDP(s: s, t: t) + print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") + + // 状态压缩后的动态规划 + res = editDistanceDPComp(s: s, t: t) + print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") + } +} diff --git a/codes/swift/chapter_dynamic_programming/knapsack.swift b/codes/swift/chapter_dynamic_programming/knapsack.swift new file mode 100644 index 000000000..93c1aed6a --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/knapsack.swift @@ -0,0 +1,110 @@ +/** + * File: knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 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) +} + +/* 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] +} + +/* 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] +} + +/* 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] +} + +@main +enum Knapsack { + /* Driver Code */ + static func main() { + let wgt = [10, 20, 30, 40, 50] + let val = [50, 120, 150, 210, 240] + let cap = 50 + let n = wgt.count + + // 暴力搜索 + var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) + print("不超过背包容量的最大物品价值为 \(res)") + + // 记忆化搜索 + var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) + res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) + print("不超过背包容量的最大物品价值为 \(res)") + + // 动态规划 + res = knapsackDP(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + + // 状态压缩后的动态规划 + res = knapsackDPComp(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift b/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift new file mode 100644 index 000000000..d4a8eefbf --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 爬楼梯最小代价:动态规划 */ +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] +} + +/* 爬楼梯最小代价:状态压缩后的动态规划 */ +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 +} + +@main +enum MinCostClimbingStairsDP { + /* Driver Code */ + static func main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print("输入楼梯的代价列表为 \(cost)") + + var res = minCostClimbingStairsDP(cost: cost) + print("爬完楼梯的最低代价为 \(res)") + + res = minCostClimbingStairsDPComp(cost: cost) + print("爬完楼梯的最低代价为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/min_path_sum.swift b/codes/swift/chapter_dynamic_programming/min_path_sum.swift new file mode 100644 index 000000000..1deaddf3b --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/min_path_sum.swift @@ -0,0 +1,123 @@ +/** + * File: min_path_sum.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最小路径和:暴力搜索 */ +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] +} + +/* 最小路径和:记忆化搜索 */ +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] +} + +/* 最小路径和:动态规划 */ +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] +} + +/* 最小路径和:状态压缩后的动态规划 */ +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] +} + +@main +enum MinPathSum { + /* Driver Code */ + static func main() { + let grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ] + let n = grid.count + let m = grid[0].count + + // 暴力搜索 + var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) + print("从左上角到右下角的做小路径和为 \(res)") + + // 记忆化搜索 + var mem = Array(repeating: Array(repeating: -1, count: m), count: n) + res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) + print("从左上角到右下角的做小路径和为 \(res)") + + // 动态规划 + res = minPathSumDP(grid: grid) + print("从左上角到右下角的做小路径和为 \(res)") + + // 状态压缩后的动态规划 + res = minPathSumDPComp(grid: grid) + print("从左上角到右下角的做小路径和为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift b/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift new file mode 100644 index 000000000..a9f4fef3e --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 完全背包:动态规划 */ +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] +} + +/* 完全背包:状态压缩后的动态规划 */ +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] +} + +@main +enum UnboundedKnapsack { + /* Driver Code */ + static func main() { + let wgt = [1, 2, 3] + let val = [5, 11, 15] + let cap = 4 + + // 动态规划 + var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + + // 状态压缩后的动态规划 + res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + } +}