diff --git a/codes/java/chapter_dynamic_programming/min_path_sum.java b/codes/java/chapter_dynamic_programming/min_path_sum.java index 37ff22d2..d8bc9923 100644 --- a/codes/java/chapter_dynamic_programming/min_path_sum.java +++ b/codes/java/chapter_dynamic_programming/min_path_sum.java @@ -104,7 +104,7 @@ public class min_path_sum { // 暴力搜索 int res = minPathSumDFS(grid, n - 1, m - 1); - System.out.println("从左上角到右下角的做小路径和为 " + res); + System.out.println("从左上角到右下角的最小路径和为 " + res); // 记忆化搜索 int[][] mem = new int[n][m]; @@ -112,14 +112,14 @@ public class min_path_sum { Arrays.fill(row, -1); } res = minPathSumDFSMem(grid, mem, n - 1, m - 1); - System.out.println("从左上角到右下角的做小路径和为 " + res); + System.out.println("从左上角到右下角的最小路径和为 " + res); // 动态规划 res = minPathSumDP(grid); - System.out.println("从左上角到右下角的做小路径和为 " + res); + System.out.println("从左上角到右下角的最小路径和为 " + res); // 空间优化后的动态规划 res = minPathSumDPComp(grid); - System.out.println("从左上角到右下角的做小路径和为 " + res); + System.out.println("从左上角到右下角的最小路径和为 " + res); } } diff --git a/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js b/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js new file mode 100644 index 00000000..67f024f1 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_constraint_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 带约束爬楼梯:动态规划 */ +function climbingStairsConstraintDP(n) { + if (n === 1 || n === 2) { + return n; + } + // 初始化 dp 表,用于存储子问题的解 + const dp = Array.from(new Array(n + 1), () => new Array(3)); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let 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]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); diff --git a/codes/javascript/chapter_dynamic_programming/coin_change.js b/codes/javascript/chapter_dynamic_programming/coin_change.js new file mode 100644 index 00000000..2aa5fc44 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/coin_change.js @@ -0,0 +1,66 @@ +/** + * File: coin_change.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零钱兑换:动态规划 */ +function coinChangeDP(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 状态转移:首行首列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* 零钱兑换:状态压缩后的动态规划 */ +function coinChangeDPComp(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 动态规划 +let res = coinChangeDP(coins, amt); +console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); + +// 状态压缩后的动态规划 +res = coinChangeDPComp(coins, amt); +console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/coin_change_ii.js b/codes/javascript/chapter_dynamic_programming/coin_change_ii.js new file mode 100644 index 00000000..92d29487 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/coin_change_ii.js @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零钱兑换 II:动态规划 */ +function coinChangeIIDP(coins, amt) { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 初始化首列 + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let 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:状态压缩后的动态规划 */ +function coinChangeIIDPComp(coins, amt) { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let 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]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// 动态规划 +let res = coinChangeIIDP(coins, amt); +console.log(`凑出目标金额的硬币组合数量为 ${res}`); + +// 状态压缩后的动态规划 +res = coinChangeIIDPComp(coins, amt); +console.log(`凑出目标金额的硬币组合数量为 ${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/edit_distance.js b/codes/javascript/chapter_dynamic_programming/edit_distance.js new file mode 100644 index 00000000..a8dd3655 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/edit_distance.js @@ -0,0 +1,138 @@ +/** + * File: edit_distance.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 编辑距离:暴力搜索 */ +function editDistanceDFS(s, t, i, j) { + // 若 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return Math.min(Math.min(insert, del), replace) + 1; +} + +/* 编辑距离:记忆化搜索 */ +function editDistanceDFSMem(s, t, mem, i, j) { + // 若 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = Math.min(Math.min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 编辑距离:动态规划 */ +function editDistanceDP(s, t) { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + // 状态转移:首行首列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = + Math.min( + Math.min(dp[i][j - 1], dp[i - 1][j]), + dp[i - 1][j - 1] + ) + 1; + } + } + } + return dp[n][m]; +} + +/* 编辑距离:状态压缩后的动态规划 */ +function editDistanceDPComp(s, t) { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 状态转移:首行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (let i = 1; i <= n; i++) { + // 状态转移:首列 + let leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; +} + +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 暴力搜索 +let res = editDistanceDFS(s, t, n, m); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 记忆化搜索 +const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 动态规划 +res = editDistanceDP(s, t); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 状态压缩后的动态规划 +res = editDistanceDPComp(s, t); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); diff --git a/codes/javascript/chapter_dynamic_programming/knapsack.js b/codes/javascript/chapter_dynamic_programming/knapsack.js new file mode 100644 index 00000000..885fc54d --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/knapsack.js @@ -0,0 +1,112 @@ +/** + * File: knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 背包:暴力搜索 */ +function knapsackDFS(wgt, val, i, c) { + // 若已选完所有物品或背包无容量,则返回价值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若超过背包容量,则只能不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return Math.max(no, yes); +} + +/* 0-1 背包:记忆化搜索 */ +function knapsackDFSMem(wgt, val, mem, i, c) { + // 若已选完所有物品或背包无容量,则返回价值 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 的最大价值 + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:动态规划 */ +function knapsackDP(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(n + 1) + .fill(0) + .map(() => Array(cap + 1).fill(0)); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:状态压缩后的动态规划 */ +function knapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(cap + 1).fill(0); + // 状态转移 + for (let i = 1; i <= n; i++) { + // 倒序遍历 + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 暴力搜索 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 记忆化搜索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 动态规划 +res = knapsackDP(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 状态压缩后的动态规划 +res = knapsackDPComp(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js b/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js new file mode 100644 index 00000000..0fc26970 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js @@ -0,0 +1,49 @@ +/** + * File: min_cost_climbing_stairs_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 爬楼梯最小代价:动态规划 */ +function minCostClimbingStairsDP(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 初始化 dp 表,用于存储子问题的解 + const dp = new Array(n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬楼梯最小代价:状态压缩后的动态规划 */ +function minCostClimbingStairsDPComp(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log('输入楼梯的代价列表为:', cost); + +let res = minCostClimbingStairsDP(cost); +console.log(`爬完楼梯的最低代价为:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`爬完楼梯的最低代价为:${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/min_path_sum.js b/codes/javascript/chapter_dynamic_programming/min_path_sum.js new file mode 100644 index 00000000..7f6a473b --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/min_path_sum.js @@ -0,0 +1,121 @@ +/** + * File: min_path_sum.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小路径和:暴力搜索 */ +function minPathSumDFS(grid, i, j) { + // 若为左上角单元格,则终止搜索 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Infinity; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + const left = minPathSumDFS(grid, i - 1, j); + const up = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return Math.min(left, up) + grid[i][j]; +} + +/* 最小路径和:记忆化搜索 */ +function minPathSumDFSMem(grid, mem, i, j) { + // 若为左上角单元格,则终止搜索 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Infinity; + } + // 若已有记录,则直接返回 + if (mem[i][j] !== -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + const left = minPathSumDFSMem(grid, mem, i - 1, j); + const up = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路径和:动态规划 */ +function minPathSumDP(grid) { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行列 + for (let i = 1; i < n; i++) { + for (let j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路径和:状态压缩后的动态规划 */ +function minPathSumDPComp(grid) { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = new Array(m); + // 状态转移:首行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (let i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +] +const n = grid.length, + m = grid[0].length; +// 暴力搜索 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 记忆化搜索 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 动态规划 +res = minPathSumDP(grid); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 状态压缩后的动态规划 +res = minPathSumDPComp(grid); +console.log(`从左上角到右下角的最小路径和为 ${res}`); \ No newline at end of file diff --git a/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js b/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js new file mode 100644 index 00000000..582ee460 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全背包:动态规划 */ +function unboundedKnapsackDP(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:状态压缩后的动态规划 */ +function unboundedKnapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 动态规划 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 状态压缩后的动态规划 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); diff --git a/codes/typescript/chapter_backtracking/n_queens.ts b/codes/typescript/chapter_backtracking/n_queens.ts index 702ce907..3aab291d 100644 --- a/codes/typescript/chapter_backtracking/n_queens.ts +++ b/codes/typescript/chapter_backtracking/n_queens.ts @@ -61,3 +61,5 @@ res.forEach((state) => { console.log('--------------------'); state.forEach((row) => console.log(row)); }); + +export {}; \ No newline at end of file diff --git a/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts b/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts new file mode 100644 index 00000000..2f3b2118 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts @@ -0,0 +1,35 @@ +/** + * File: climbing_stairs_constraint_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 带约束爬楼梯:动态规划 */ +function climbingStairsConstraintDP(n: number): number { + if (n === 1 || n === 2) { + return n; + } + // 初始化 dp 表,用于存储子问题的解 + const dp = Array.from( + { length: n + 1 }, + () => new Array(3) + ); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let 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]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/coin_change.ts b/codes/typescript/chapter_dynamic_programming/coin_change.ts new file mode 100644 index 00000000..09141cd7 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/coin_change.ts @@ -0,0 +1,68 @@ +/** + * File: coin_change.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零钱兑换:动态规划 */ +function coinChangeDP(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 状态转移:首行首列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* 零钱兑换:状态压缩后的动态规划 */ +function coinChangeDPComp(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 动态规划 +let res = coinChangeDP(coins, amt); +console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); + +// 状态压缩后的动态规划 +res = coinChangeDPComp(coins, amt); +console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts b/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts new file mode 100644 index 00000000..55ba0af4 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零钱兑换 II:动态规划 */ +function coinChangeIIDP(coins: Array, amt: number): number { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 初始化首列 + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let 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:状态压缩后的动态规划 */ +function coinChangeIIDPComp(coins: Array, amt: number): number { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let 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]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// 动态规划 +let res = coinChangeIIDP(coins, amt); +console.log(`凑出目标金额的硬币组合数量为 ${res}`); + +// 状态压缩后的动态规划 +res = coinChangeIIDPComp(coins, amt); +console.log(`凑出目标金额的硬币组合数量为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/edit_distance.ts b/codes/typescript/chapter_dynamic_programming/edit_distance.ts new file mode 100644 index 00000000..ef91b13a --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/edit_distance.ts @@ -0,0 +1,151 @@ +/** + * File: edit_distance.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 编辑距离:暴力搜索 */ +function editDistanceDFS(s: string, t: string, i: number, j: number): number { + // 若 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return Math.min(Math.min(insert, del), replace) + 1; +} + +/* 编辑距离:记忆化搜索 */ +function editDistanceDFSMem( + s: string, + t: string, + mem: Array>, + i: number, + j: number +): number { + // 若 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = Math.min(Math.min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 编辑距离:动态规划 */ +function editDistanceDP(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => 0) + ); + // 状态转移:首行首列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = + Math.min( + Math.min(dp[i][j - 1], dp[i - 1][j]), + dp[i - 1][j - 1] + ) + 1; + } + } + } + return dp[n][m]; +} + +/* 编辑距离:状态压缩后的动态规划 */ +function editDistanceDPComp(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 状态转移:首行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (let i = 1; i <= n; i++) { + // 状态转移:首列 + let leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 暴力搜索 +let res = editDistanceDFS(s, t, n, m); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 记忆化搜索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => -1) +); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 动态规划 +res = editDistanceDP(s, t); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 状态压缩后的动态规划 +res = editDistanceDPComp(s, t); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/knapsack.ts b/codes/typescript/chapter_dynamic_programming/knapsack.ts new file mode 100644 index 00000000..39ced7f3 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/knapsack.ts @@ -0,0 +1,135 @@ +/** + * File: knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 背包:暴力搜索 */ +function knapsackDFS( + wgt: Array, + val: Array, + i: number, + c: number +): number { + // 若已选完所有物品或背包无容量,则返回价值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若超过背包容量,则只能不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = + knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return Math.max(no, yes); +} + +/* 0-1 背包:记忆化搜索 */ +function knapsackDFSMem( + wgt: Array, + val: Array, + mem: Array>, + i: number, + c: number +): number { + // 若已选完所有物品或背包无容量,则返回价值 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 的最大价值 + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:动态规划 */ +function knapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:状态压缩后的动态规划 */ +function knapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(cap + 1).fill(0); + // 状态转移 + for (let i = 1; i <= n; i++) { + // 倒序遍历 + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 暴力搜索 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 记忆化搜索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 动态规划 +res = knapsackDP(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 状态压缩后的动态规划 +res = knapsackDPComp(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts b/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts new file mode 100644 index 00000000..ade829b5 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 爬楼梯最小代价:动态规划 */ +function minCostClimbingStairsDP(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 初始化 dp 表,用于存储子问题的解 + const dp = new Array(n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬楼梯最小代价:状态压缩后的动态规划 */ +function minCostClimbingStairsDPComp(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log(`输入楼梯的代价列表为:${cost}`); + +let res = minCostClimbingStairsDP(cost); +console.log(`爬完楼梯的最低代价为:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`爬完楼梯的最低代价为:${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/min_path_sum.ts b/codes/typescript/chapter_dynamic_programming/min_path_sum.ts new file mode 100644 index 00000000..b0a08df2 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/min_path_sum.ts @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小路径和:暴力搜索 */ +function minPathSumDFS( + grid: Array>, + i: number, + j: number +): number { + // 若为左上角单元格,则终止搜索 + if (i === 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Infinity; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + const left = minPathSumDFS(grid, i - 1, j); + const up = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return Math.min(left, up) + grid[i][j]; +} + +/* 最小路径和:记忆化搜索 */ +function minPathSumDFSMem( + grid: Array>, + mem: Array>, + i: number, + j: number +): number { + // 若为左上角单元格,则终止搜索 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Infinity; + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + const left = minPathSumDFSMem(grid, mem, i - 1, j); + const up = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路径和:动态规划 */ +function minPathSumDP(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行列 + for (let i = 1; i < n; i++) { + for (let j: number = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路径和:状态压缩后的动态规划 */ +function minPathSumDPComp(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = new Array(m); + // 状态转移:首行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (let i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 暴力搜索 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 记忆化搜索 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 动态规划 +res = minPathSumDP(grid); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 状态压缩后的动态规划 +res = minPathSumDPComp(grid); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts b/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts new file mode 100644 index 00000000..9005b99b --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts @@ -0,0 +1,73 @@ +/** + * File: unbounded_knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全背包:动态规划 */ +function unboundedKnapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:状态压缩后的动态规划 */ +function unboundedKnapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 动态规划 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 状态压缩后的动态规划 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +export {};