From c1adeb239905dc9f4b2fd408c4980c933c3c78a9 Mon Sep 17 00:00:00 2001 From: Reanon <793584285@qq.com> Date: Mon, 24 Jul 2023 03:08:35 +0800 Subject: [PATCH] feat(go/dp): support dynamic programming (#622) * feat(go/dp): support climbing stairs * feat(go/dp): support knapsack * feat(go/dp): coin_change & edit_distance --- .../climbing_stairs_backtrack.go | 36 +++++ .../climbing_stairs_constraint_dp.go | 25 ++++ .../climbing_stairs_dfs.go | 21 +++ .../climbing_stairs_dfs_mem.go | 32 +++++ .../climbing_stairs_dp.go | 35 +++++ .../climbing_stairs_test.go | 57 ++++++++ .../coin_change.go | 66 +++++++++ .../coin_change_ii.go | 54 ++++++++ .../coin_change_test.go | 23 ++++ .../edit_distance.go | 129 ++++++++++++++++++ .../edit_distance_test.go | 40 ++++++ .../chapter_dynamic_programming/knapsack.go | 87 ++++++++++++ .../knapsack_test.go | 54 ++++++++ .../min_cost_climbing_stairs_dp.go | 42 ++++++ .../min_path_sum.go | 94 +++++++++++++ .../min_path_sum_test.go | 43 ++++++ .../unbounded_knapsack.go | 50 +++++++ 17 files changed, 888 insertions(+) create mode 100644 codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go create mode 100644 codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go create mode 100644 codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go create mode 100644 codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go create mode 100644 codes/go/chapter_dynamic_programming/climbing_stairs_dp.go create mode 100644 codes/go/chapter_dynamic_programming/climbing_stairs_test.go create mode 100644 codes/go/chapter_dynamic_programming/coin_change.go create mode 100644 codes/go/chapter_dynamic_programming/coin_change_ii.go create mode 100644 codes/go/chapter_dynamic_programming/coin_change_test.go create mode 100644 codes/go/chapter_dynamic_programming/edit_distance.go create mode 100644 codes/go/chapter_dynamic_programming/edit_distance_test.go create mode 100644 codes/go/chapter_dynamic_programming/knapsack.go create mode 100644 codes/go/chapter_dynamic_programming/knapsack_test.go create mode 100644 codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go create mode 100644 codes/go/chapter_dynamic_programming/min_path_sum.go create mode 100644 codes/go/chapter_dynamic_programming/min_path_sum_test.go create mode 100644 codes/go/chapter_dynamic_programming/unbounded_knapsack.go diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go b/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go new file mode 100644 index 000000000..8049775d5 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go @@ -0,0 +1,36 @@ +// File: climbing_stairs_backtrack.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 回溯 */ +func backtrack(choices []int, state, n int, res []int) { + // 当爬到第 n 阶时,方案数量加 1 + if state == n { + res[0] = res[0] + 1 + } + // 遍历所有选择 + for _, choice := range choices { + // 剪枝:不允许越过第 n 阶 + if state+choice > n { + break + } + // 尝试:做出选择,更新状态 + backtrack(choices, state+choice, n, res) + // 回退 + } +} + +/* 爬楼梯:回溯 */ +func climbingStairsBacktrack(n int) int { + // 可选择向上爬 1 或 2 阶 + choices := []int{1, 2} + // 从第 0 阶开始爬 + state := 0 + res := make([]int, 1) + // 使用 res[0] 记录方案数量 + res[0] = 0 + backtrack(choices, state, n, res) + return res[0] +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go b/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go new file mode 100644 index 000000000..2621b3939 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go @@ -0,0 +1,25 @@ +// File: climbing_stairs_constraint_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 带约束爬楼梯:动态规划 */ +func climbingStairsConstraintDP(n int) int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用于存储子问题的解 + dp := make([][3]int, n+1) + // 初始状态:预设最小子问题的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + 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] +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go b/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go new file mode 100644 index 000000000..62310483e --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go @@ -0,0 +1,21 @@ +// File: climbing_stairs_dfs.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 搜索 */ +func dfs(i int) int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfs(i-1) + dfs(i-2) + return count +} + +/* 爬楼梯:搜索 */ +func climbingStairsDFS(n int) int { + return dfs(n) +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go b/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go new file mode 100644 index 000000000..03d5158e5 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go @@ -0,0 +1,32 @@ +// File: climbing_stairs_dfs_mem.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 记忆化搜索 */ +func dfsMem(i int, mem []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] + count := dfsMem(i-1, mem) + dfsMem(i-2, mem) + // 记录 dp[i] + mem[i] = count + return count +} + +/* 爬楼梯:记忆化搜索 */ +func climbingStairsDFSMem(n int) int { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + mem := make([]int, n+1) + for i := range mem { + mem[i] = -1 + } + return dfsMem(n, mem) +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go b/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go new file mode 100644 index 000000000..4cb060b9b --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go @@ -0,0 +1,35 @@ +// File: climbing_stairs_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 爬楼梯:动态规划 */ +func climbingStairsDP(n int) int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用于存储子问题的解 + dp := make([]int, n+1) + // 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + dp[i] = dp[i-1] + dp[i-2] + } + return dp[n] +} + +/* 爬楼梯:状态压缩后的动态规划 */ +func climbingStairsDPComp(n int) int { + if n == 1 || n == 2 { + return n + } + a, b := 1, 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + a, b = b, a+b + } + return b +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_test.go b/codes/go/chapter_dynamic_programming/climbing_stairs_test.go new file mode 100644 index 000000000..7599e11ca --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_test.go @@ -0,0 +1,57 @@ +// File: climbing_stairs_test.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestClimbingStairsBacktrack(t *testing.T) { + n := 9 + res := climbingStairsBacktrack(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsDFS(t *testing.T) { + n := 9 + res := climbingStairsDFS(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsDFSMem(t *testing.T) { + n := 9 + res := climbingStairsDFSMem(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsDP(t *testing.T) { + n := 9 + res := climbingStairsDP(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsDPComp(t *testing.T) { + n := 9 + res := climbingStairsDPComp(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsConstraintDP(t *testing.T) { + n := 9 + res := climbingStairsConstraintDP(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestMinCostClimbingStairsDPComp(t *testing.T) { + cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} + fmt.Printf("输入楼梯的代价列表为 %v\n", cost) + + res := minCostClimbingStairsDP(cost) + fmt.Printf("爬完楼梯的最低代价为 %d\n", res) + + res = minCostClimbingStairsDPComp(cost) + fmt.Printf("爬完楼梯的最低代价为 %d\n", res) +} diff --git a/codes/go/chapter_dynamic_programming/coin_change.go b/codes/go/chapter_dynamic_programming/coin_change.go new file mode 100644 index 000000000..e2b0c6e76 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/coin_change.go @@ -0,0 +1,66 @@ +// File: coin_change.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 零钱兑换:动态规划 */ +func coinChangeDP(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 状态转移:首行首列 + for a := 1; a <= amt; a++ { + dp[0][a] = max + } + // 状态转移:其余行列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i-1][a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) + } + } + } + if dp[n][amt] != max { + return dp[n][amt] + } + return -1 +} + +/* 零钱兑换:动态规划 */ +func coinChangeDPComp(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // 初始化 dp 表 + dp := make([]int, amt+1) + for i := 1; i <= amt; i++ { + dp[i] = max + } + // 状态转移 + for i := 1; i <= n; i++ { + // 倒序遍历 + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) + } + } + } + if dp[amt] != max { + return dp[amt] + } + return -1 +} diff --git a/codes/go/chapter_dynamic_programming/coin_change_ii.go b/codes/go/chapter_dynamic_programming/coin_change_ii.go new file mode 100644 index 000000000..b4bccd50e --- /dev/null +++ b/codes/go/chapter_dynamic_programming/coin_change_ii.go @@ -0,0 +1,54 @@ +// File: coin_change_ii.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 零钱兑换 II:动态规划 */ +func coinChangeIIDP(coins []int, amt int) int { + n := len(coins) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 初始化首列 + for i := 0; i <= n; i++ { + dp[i][0] = 1 + } + // 状态转移:其余行列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + 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 { + n := len(coins) + // 初始化 dp 表 + dp := make([]int, amt+1) + dp[0] = 1 + // 状态转移 + for i := 1; i <= n; i++ { + // 倒序遍历 + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a-coins[i-1]] + } + } + } + return dp[amt] +} diff --git a/codes/go/chapter_dynamic_programming/coin_change_test.go b/codes/go/chapter_dynamic_programming/coin_change_test.go new file mode 100644 index 000000000..81cbba9c7 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/coin_change_test.go @@ -0,0 +1,23 @@ +// File: coin_change_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestCoinChange(t *testing.T) { + coins := []int{1, 2, 5} + amt := 4 + + // 动态规划 + res := coinChangeDP(coins, amt) + fmt.Printf("凑到目标金额所需的最少硬币数量为 %d\n", res) + + // 状态压缩后的动态规划 + res = coinChangeDPComp(coins, amt) + fmt.Printf("凑到目标金额所需的最少硬币数量为 %d\n", res) +} diff --git a/codes/go/chapter_dynamic_programming/edit_distance.go b/codes/go/chapter_dynamic_programming/edit_distance.go new file mode 100644 index 000000000..b34f1b582 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/edit_distance.go @@ -0,0 +1,129 @@ +// File: edit_distance.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 编辑距离:暴力搜索 */ +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[i-1] == t[j-1] { + return editDistanceDFS(s, t, i-1, j-1) + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert := editDistanceDFS(s, t, i, j-1) + deleted := editDistanceDFS(s, t, i-1, j) + replace := editDistanceDFS(s, t, i-1, j-1) + // 返回最少编辑步数 + return MinInt(MinInt(insert, deleted), replace) + 1 +} + +/* 编辑距离:记忆化搜索 */ +func editDistanceDFSMem(s string, t string, mem [][]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[i-1] == t[j-1] { + return editDistanceDFSMem(s, t, mem, i-1, j-1) + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert := editDistanceDFSMem(s, t, mem, i, j-1) + deleted := editDistanceDFSMem(s, t, mem, i-1, j) + replace := editDistanceDFSMem(s, t, mem, i-1, j-1) + // 记录并返回最少编辑步数 + mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 + return mem[i][j] +} + +/* 编辑距离:动态规划 */ +func editDistanceDP(s string, t string) int { + n := len(s) + m := len(t) + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, m+1) + } + // 状态转移:首行首列 + for i := 1; i <= n; i++ { + dp[i][0] = i + } + for j := 1; j <= m; j++ { + dp[0][j] = j + } + // 状态转移:其余行列 + for i := 1; i <= n; i++ { + for j := 1; j <= m; j++ { + if s[i-1] == t[j-1] { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i-1][j-1] + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = MinInt(MinInt(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 { + n := len(s) + m := len(t) + dp := make([]int, m+1) + // 状态转移:首行 + for j := 1; j <= m; j++ { + dp[j] = j + } + // 状态转移:其余行 + for i := 1; i <= n; i++ { + // 状态转移:首列 + leftUp := dp[0] // 暂存 dp[i-1, j-1] + dp[0] = i + // 状态转移:其余列 + for j := 1; j <= m; j++ { + temp := dp[j] + if s[i-1] == t[j-1] { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftUp + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 + } + leftUp = temp // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m] +} + +func MinInt(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/codes/go/chapter_dynamic_programming/edit_distance_test.go b/codes/go/chapter_dynamic_programming/edit_distance_test.go new file mode 100644 index 000000000..b75f2a10e --- /dev/null +++ b/codes/go/chapter_dynamic_programming/edit_distance_test.go @@ -0,0 +1,40 @@ +// File: edit_distance_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestEditDistanceDFS(test *testing.T) { + s := "bag" + t := "pack" + n := len(s) + m := len(t) + + // 暴力搜索 + res := editDistanceDFS(s, t, n, m) + fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) + + // 记忆化搜索 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, m+1) + for j := 0; j <= m; j++ { + mem[i][j] = -1 + } + } + res = editDistanceDFSMem(s, t, mem, n, m) + fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) + + // 动态规划 + res = editDistanceDP(s, t) + fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) + + // 状态压缩后的动态规划 + res = editDistanceDPComp(s, t) + fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) +} diff --git a/codes/go/chapter_dynamic_programming/knapsack.go b/codes/go/chapter_dynamic_programming/knapsack.go new file mode 100644 index 000000000..64fbf33c7 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/knapsack.go @@ -0,0 +1,87 @@ +// File: knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 0-1 背包:暴力搜索 */ +func knapsackDFS(wgt, val []int, i, c int) int { + // 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超过背包容量,则只能不放入背包 + if wgt[i-1] > c { + return knapsackDFS(wgt, val, i-1, c) + } + // 计算不放入和放入物品 i 的最大价值 + no := knapsackDFS(wgt, val, i-1, c) + yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] + // 返回两种方案中价值更大的那一个 + return int(math.Max(float64(no), float64(yes))) +} + +/* 0-1 背包:记忆化搜索 */ +func knapsackDFSMem(wgt, val []int, mem [][]int, i, 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, val, mem, i-1, c) + } + // 计算不放入和放入物品 i 的最大价值 + no := knapsackDFSMem(wgt, val, mem, i-1, c) + yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] + // 返回两种方案中价值更大的那一个 + mem[i][c] = int(math.Max(float64(no), float64(yes))) + return mem[i][c] +} + +/* 0-1 背包:动态规划 */ +func knapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 状态转移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i-1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:状态压缩后的动态规划 */ +func knapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([]int, cap+1) + // 状态转移 + for i := 1; i <= n; i++ { + // 倒序遍历 + for c := cap; c >= 1; c-- { + if wgt[i-1] <= c { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/codes/go/chapter_dynamic_programming/knapsack_test.go b/codes/go/chapter_dynamic_programming/knapsack_test.go new file mode 100644 index 000000000..8d1d9deb5 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/knapsack_test.go @@ -0,0 +1,54 @@ +// File: knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + c := 50 + n := len(wgt) + + // 暴力搜索 + res := knapsackDFS(wgt, val, n, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) + + // 记忆化搜索 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, c+1) + for j := 0; j <= c; j++ { + mem[i][j] = -1 + } + } + res = knapsackDFSMem(wgt, val, mem, n, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) + + // 动态规划 + res = knapsackDP(wgt, val, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) + + // 状态压缩后的动态规划 + res = knapsackDPComp(wgt, val, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) +} + +func TestUnboundedKnapsack(t *testing.T) { + wgt := []int{1, 2, 3} + val := []int{5, 11, 15} + c := 4 + + // 动态规划 + res := unboundedKnapsackDP(wgt, val, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) + + // 状态压缩后的动态规划 + res = unboundedKnapsackDPComp(wgt, val, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) +} diff --git a/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go b/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go new file mode 100644 index 000000000..b7c597eb3 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go @@ -0,0 +1,42 @@ +// File: min_cost_climbing_stairs_dp.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 爬楼梯最小代价:动态规划 */ +func minCostClimbingStairsDP(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + // 初始化 dp 表,用于存储子问题的解 + dp := make([]int, n+1) + // 初始状态:预设最小子问题的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + dp[i] = int(math.Min(float64(dp[i-1]), float64(dp[i-2]+cost[i]))) + } + return dp[n] +} + +/* 爬楼梯最小代价:状态压缩后的动态规划 */ +func minCostClimbingStairsDPComp(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + // 初始状态:预设最小子问题的解 + a, b := cost[1], cost[2] + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + tmp := b + b = int(math.Min(float64(a), float64(tmp+cost[i]))) + a = tmp + } + return b +} diff --git a/codes/go/chapter_dynamic_programming/min_path_sum.go b/codes/go/chapter_dynamic_programming/min_path_sum.go new file mode 100644 index 000000000..366cba856 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/min_path_sum.go @@ -0,0 +1,94 @@ +// File: min_path_sum.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 最小路径和:暴力搜索 */ +func minPathSumDFS(grid [][]int, i, j int) int { + // 若为左上角单元格,则终止搜索 + if i == 0 && j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return math.MaxInt + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + left := minPathSumDFS(grid, i-1, j) + up := minPathSumDFS(grid, i, j-1) + // 返回从左上角到 (i, j) 的最小路径代价 + return int(math.Min(float64(left), float64(up))) + grid[i][j] +} + +/* 最小路径和:记忆化搜索 */ +func minPathSumDFSMem(grid, mem [][]int, i, j int) int { + // 若为左上角单元格,则终止搜索 + if i == 0 && j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return math.MaxInt + } + // 若已有记录,则直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左边和上边单元格的最小路径代价 + left := minPathSumDFSMem(grid, mem, i-1, j) + up := minPathSumDFSMem(grid, mem, i, j-1) + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] + return mem[i][j] +} + +/* 最小路径和:动态规划 */ +func minPathSumDP(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // 初始化 dp 表 + dp := make([][]int, n) + for i := 0; i < n; i++ { + dp[i] = make([]int, m) + } + dp[0][0] = grid[0][0] + // 状态转移:首行 + for j := 1; j < m; j++ { + dp[0][j] = dp[0][j-1] + grid[0][j] + } + // 状态转移:首列 + for i := 1; i < n; i++ { + dp[i][0] = dp[i-1][0] + grid[i][0] + } + // 状态转移:其余行列 + for i := 1; i < n; i++ { + for j := 1; j < m; j++ { + dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] + } + } + return dp[n-1][m-1] +} + +/* 最小路径和:状态压缩后的动态规划 */ +func minPathSumDPComp(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // 初始化 dp 表 + dp := make([]int, m) + // 状态转移:首行 + dp[0] = grid[0][0] + for j := 1; j < m; j++ { + dp[j] = dp[j-1] + grid[0][j] + } + // 状态转移:其余行列 + for i := 1; i < n; i++ { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + // 状态转移:其余列 + for j := 1; j < m; j++ { + dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] + } + } + return dp[m-1] +} diff --git a/codes/go/chapter_dynamic_programming/min_path_sum_test.go b/codes/go/chapter_dynamic_programming/min_path_sum_test.go new file mode 100644 index 000000000..c4d43ef04 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/min_path_sum_test.go @@ -0,0 +1,43 @@ +// File: min_path_sum_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestMinPathSum(t *testing.T) { + grid := [][]int{ + {1, 3, 1, 5}, + {2, 2, 4, 2}, + {5, 3, 2, 1}, + {4, 3, 5, 2}, + } + n, m := len(grid), len(grid[0]) + + // 暴力搜索 + res := minPathSumDFS(grid, n-1, m-1) + fmt.Printf("从左上角到右下角的做小路径和为 %d\n", res) + + // 记忆化搜索 + mem := make([][]int, n) + for i := 0; i < n; i++ { + mem[i] = make([]int, m) + for j := 0; j < m; j++ { + mem[i][j] = -1 + } + } + res = minPathSumDFSMem(grid, mem, n-1, m-1) + fmt.Printf("从左上角到右下角的做小路径和为 %d\n", res) + + // 动态规划 + res = minPathSumDP(grid) + fmt.Printf("从左上角到右下角的做小路径和为 %d\n", res) + + // 状态压缩后的动态规划 + res = minPathSumDPComp(grid) + fmt.Printf("从左上角到右下角的做小路径和为 %d\n", res) +} diff --git a/codes/go/chapter_dynamic_programming/unbounded_knapsack.go b/codes/go/chapter_dynamic_programming/unbounded_knapsack.go new file mode 100644 index 000000000..6600f334e --- /dev/null +++ b/codes/go/chapter_dynamic_programming/unbounded_knapsack.go @@ -0,0 +1,50 @@ +// File: unbounded_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 完全背包:动态规划 */ +func unboundedKnapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 状态转移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i-1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 完全背包:状态压缩后的动态规划 */ +func unboundedKnapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([]int, cap+1) + // 状态转移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +}