Fix a definition.

This commit is contained in:
krahets 2023-08-27 00:50:18 +08:00
parent debf42b189
commit 9731a46d67
85 changed files with 167 additions and 159 deletions

View File

@ -22,7 +22,7 @@ int climbingStairsDP(int n) {
return dp[n];
}
/* 爬楼梯:状态压缩后的动态规划 */
/* 爬楼梯:空间优化后的动态规划 */
int climbingStairsDPComp(int n) {
if (n == 1 || n == 2)
return n;

View File

@ -31,7 +31,7 @@ int coinChangeDP(vector<int> &coins, int amt) {
return dp[n][amt] != MAX ? dp[n][amt] : -1;
}
/* 零钱兑换:状态压缩后的动态规划 */
/* 零钱兑换:空间优化后的动态规划 */
int coinChangeDPComp(vector<int> &coins, int amt) {
int n = coins.size();
int MAX = amt + 1;
@ -62,7 +62,7 @@ int main() {
int res = coinChangeDP(coins, amt);
cout << "凑到目标金额所需的最少硬币数量为 " << res << endl;
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = coinChangeDPComp(coins, amt);
cout << "凑到目标金额所需的最少硬币数量为 " << res << endl;

View File

@ -30,7 +30,7 @@ int coinChangeIIDP(vector<int> &coins, int amt) {
return dp[n][amt];
}
/* 零钱兑换 II状态压缩后的动态规划 */
/* 零钱兑换 II空间优化后的动态规划 */
int coinChangeIIDPComp(vector<int> &coins, int amt) {
int n = coins.size();
// 初始化 dp 表
@ -60,7 +60,7 @@ int main() {
int res = coinChangeIIDP(coins, amt);
cout << "凑出目标金额的硬币组合数量为 " << res << endl;
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = coinChangeIIDPComp(coins, amt);
cout << "凑出目标金额的硬币组合数量为 " << res << endl;

View File

@ -80,7 +80,7 @@ int editDistanceDP(string s, string t) {
return dp[n][m];
}
/* 编辑距离:状态压缩后的动态规划 */
/* 编辑距离:空间优化后的动态规划 */
int editDistanceDPComp(string s, string t) {
int n = s.length(), m = t.length();
vector<int> dp(m + 1, 0);
@ -128,7 +128,7 @@ int main() {
res = editDistanceDP(s, t);
cout << "" << s << " 更改为 " << t << " 最少需要编辑 " << res << "\n";
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = editDistanceDPComp(s, t);
cout << "" << s << " 更改为 " << t << " 最少需要编辑 " << res << "\n";

View File

@ -63,7 +63,7 @@ int knapsackDP(vector<int> &wgt, vector<int> &val, int cap) {
return dp[n][cap];
}
/* 0-1 背包:状态压缩后的动态规划 */
/* 0-1 背包:空间优化后的动态规划 */
int knapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {
int n = wgt.size();
// 初始化 dp 表
@ -101,7 +101,7 @@ int main() {
res = knapsackDP(wgt, val, cap);
cout << "不超过背包容量的最大物品价值为 " << res << endl;
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = knapsackDPComp(wgt, val, cap);
cout << "不超过背包容量的最大物品价值为 " << res << endl;

View File

@ -23,7 +23,7 @@ int minCostClimbingStairsDP(vector<int> &cost) {
return dp[n];
}
/* 爬楼梯最小代价:状态压缩后的动态规划 */
/* 爬楼梯最小代价:空间优化后的动态规划 */
int minCostClimbingStairsDPComp(vector<int> &cost) {
int n = cost.size() - 1;
if (n == 1 || n == 2)

View File

@ -68,7 +68,7 @@ int minPathSumDP(vector<vector<int>> &grid) {
return dp[n - 1][m - 1];
}
/* 最小路径和:状态压缩后的动态规划 */
/* 最小路径和:空间优化后的动态规划 */
int minPathSumDPComp(vector<vector<int>> &grid) {
int n = grid.size(), m = grid[0].size();
// 初始化 dp 表
@ -108,7 +108,7 @@ int main() {
res = minPathSumDP(grid);
cout << "从左上角到右下角的最小路径和为 " << res << endl;
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = minPathSumDPComp(grid);
cout << "从左上角到右下角的最小路径和为 " << res << endl;

View File

@ -26,7 +26,7 @@ int unboundedKnapsackDP(vector<int> &wgt, vector<int> &val, int cap) {
return dp[n][cap];
}
/* 完全背包:状态压缩后的动态规划 */
/* 完全背包:空间优化后的动态规划 */
int unboundedKnapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {
int n = wgt.size();
// 初始化 dp 表
@ -56,7 +56,7 @@ int main() {
int res = unboundedKnapsackDP(wgt, val, cap);
cout << "不超过背包容量的最大物品价值为 " << res << endl;
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = unboundedKnapsackDPComp(wgt, val, cap);
cout << "不超过背包容量的最大物品价值为 " << res << endl;

View File

@ -23,7 +23,7 @@ public class climbing_stairs_dp {
return dp[n];
}
/* 爬楼梯:状态压缩后的动态规划 */
/* 爬楼梯:空间优化后的动态规划 */
public int climbingStairsDPComp(int n) {
if (n == 1 || n == 2)
return n;

View File

@ -32,7 +32,7 @@ public class coin_change {
return dp[n, amt] != MAX ? dp[n, amt] : -1;
}
/* 零钱兑换:状态压缩后的动态规划 */
/* 零钱兑换:空间优化后的动态规划 */
public int coinChangeDPComp(int[] coins, int amt) {
int n = coins.Length;
int MAX = amt + 1;
@ -64,7 +64,7 @@ public class coin_change {
int res = coinChangeDP(coins, amt);
Console.WriteLine("凑到目标金额所需的最少硬币数量为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = coinChangeDPComp(coins, amt);
Console.WriteLine("凑到目标金额所需的最少硬币数量为 " + res);
}

View File

@ -31,7 +31,7 @@ public class coin_change_ii {
return dp[n, amt];
}
/* 零钱兑换 II状态压缩后的动态规划 */
/* 零钱兑换 II空间优化后的动态规划 */
public int coinChangeIIDPComp(int[] coins, int amt) {
int n = coins.Length;
// 初始化 dp 表
@ -61,7 +61,7 @@ public class coin_change_ii {
int res = coinChangeIIDP(coins, amt);
Console.WriteLine("凑出目标金额的硬币组合数量为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = coinChangeIIDPComp(coins, amt);
Console.WriteLine("凑出目标金额的硬币组合数量为 " + res);
}

View File

@ -81,7 +81,7 @@ public class edit_distance {
return dp[n, m];
}
/* 编辑距离:状态压缩后的动态规划 */
/* 编辑距离:空间优化后的动态规划 */
public int editDistanceDPComp(string s, string t) {
int n = s.Length, m = t.Length;
int[] dp = new int[m + 1];
@ -134,7 +134,7 @@ public class edit_distance {
res = editDistanceDP(s, t);
Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步");
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = editDistanceDPComp(s, t);
Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步");
}

View File

@ -66,7 +66,7 @@ public class knapsack {
return dp[n, cap];
}
/* 0-1 背包:状态压缩后的动态规划 */
/* 0-1 背包:空间优化后的动态规划 */
public int knapsackDPComp(int[] weight, int[] val, int cap) {
int n = weight.Length;
// 初始化 dp 表
@ -111,7 +111,7 @@ public class knapsack {
res = knapsackDP(weight, val, cap);
Console.WriteLine("不超过背包容量的最大物品价值为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = knapsackDPComp(weight, val, cap);
Console.WriteLine("不超过背包容量的最大物品价值为 " + res);
}

View File

@ -24,7 +24,7 @@ public class min_cost_climbing_stairs_dp {
return dp[n];
}
/* 爬楼梯最小代价:状态压缩后的动态规划 */
/* 爬楼梯最小代价:空间优化后的动态规划 */
public int minCostClimbingStairsDPComp(int[] cost) {
int n = cost.Length - 1;
if (n == 1 || n == 2)

View File

@ -69,7 +69,7 @@ public class min_path_sum {
return dp[n - 1, m - 1];
}
/* 最小路径和:状态压缩后的动态规划 */
/* 最小路径和:空间优化后的动态规划 */
public int minPathSumDPComp(int[][] grid) {
int n = grid.Length, m = grid[0].Length;
// 初始化 dp 表
@ -120,7 +120,7 @@ public class min_path_sum {
res = minPathSumDP(grid);
Console.WriteLine("从左上角到右下角的做小路径和为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = minPathSumDPComp(grid);
Console.WriteLine("从左上角到右下角的做小路径和为 " + res);
}

View File

@ -27,7 +27,7 @@ public class unbounded_knapsack {
return dp[n, cap];
}
/* 完全背包:状态压缩后的动态规划 */
/* 完全背包:空间优化后的动态规划 */
public int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {
int n = wgt.Length;
// 初始化 dp 表
@ -57,7 +57,7 @@ public class unbounded_knapsack {
int res = unboundedKnapsackDP(wgt, val, cap);
Console.WriteLine("不超过背包容量的最大物品价值为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = unboundedKnapsackDPComp(wgt, val, cap);
Console.WriteLine("不超过背包容量的最大物品价值为 " + res);
}

View File

@ -19,7 +19,7 @@ int climbingStairsDP(int n) {
return dp[n];
}
/* 爬楼梯:状态压缩后的动态规划 */
/* 爬楼梯:空间优化后的动态规划 */
int climbingStairsDPComp(int n) {
if (n == 1 || n == 2) return n;
int a = 1, b = 2;

View File

@ -31,7 +31,7 @@ int coinChangeDP(List<int> coins, int amt) {
return dp[n][amt] != MAX ? dp[n][amt] : -1;
}
/* 零钱兑换:状态压缩后的动态规划 */
/* 零钱兑换:空间优化后的动态规划 */
int coinChangeDPComp(List<int> coins, int amt) {
int n = coins.length;
int MAX = amt + 1;
@ -62,7 +62,7 @@ void main() {
int res = coinChangeDP(coins, amt);
print("凑到目标金额所需的最少硬币数量为 $res");
//
//
res = coinChangeDPComp(coins, amt);
print("凑到目标金额所需的最少硬币数量为 $res");
}

View File

@ -28,7 +28,7 @@ int coinChangeIIDP(List<int> coins, int amt) {
return dp[n][amt];
}
/* 零钱兑换 II状态压缩后的动态规划 */
/* 零钱兑换 II空间优化后的动态规划 */
int coinChangeIIDPComp(List<int> coins, int amt) {
int n = coins.length;
// dp
@ -58,7 +58,7 @@ void main() {
int res = coinChangeIIDP(coins, amt);
print("凑出目标金额的硬币组合数量为 $res");
//
//
res = coinChangeIIDPComp(coins, amt);
print("凑出目标金额的硬币组合数量为 $res");
}

View File

@ -71,7 +71,7 @@ int editDistanceDP(String s, String t) {
return dp[n][m];
}
/* 编辑距离:状态压缩后的动态规划 */
/* 编辑距离:空间优化后的动态规划 */
int editDistanceDPComp(String s, String t) {
int n = s.length, m = t.length;
List<int> dp = List.filled(m + 1, 0);
@ -119,7 +119,7 @@ void main() {
res = editDistanceDP(s, t);
print("" + s + " 更改为 " + t + " 最少需要编辑 $res");
//
//
res = editDistanceDPComp(s, t);
print("" + s + " 更改为 " + t + " 最少需要编辑 $res");
}

View File

@ -71,7 +71,7 @@ int knapsackDP(List<int> wgt, List<int> val, int cap) {
return dp[n][cap];
}
/* 0-1 背包:状态压缩后的动态规划 */
/* 0-1 背包:空间优化后的动态规划 */
int knapsackDPComp(List<int> wgt, List<int> val, int cap) {
int n = wgt.length;
// dp
@ -110,7 +110,7 @@ void main() {
res = knapsackDP(wgt, val, cap);
print("不超过背包容量的最大物品价值为 $res");
//
//
res = knapsackDPComp(wgt, val, cap);
print("不超过背包容量的最大物品价值为 $res");
}

View File

@ -22,7 +22,7 @@ int minCostClimbingStairsDP(List<int> cost) {
return dp[n];
}
/* 爬楼梯最小代价:状态压缩后的动态规划 */
/* 爬楼梯最小代价:空间优化后的动态规划 */
int minCostClimbingStairsDPComp(List<int> cost) {
int n = cost.length - 1;
if (n == 1 || n == 2) return cost[n];

View File

@ -70,7 +70,7 @@ int minPathSumDP(List<List<int>> grid) {
return dp[n - 1][m - 1];
}
/* 最小路径和:状态压缩后的动态规划 */
/* 最小路径和:空间优化后的动态规划 */
int minPathSumDPComp(List<List<int>> grid) {
int n = grid.length, m = grid[0].length;
// dp
@ -114,7 +114,7 @@ void main() {
res = minPathSumDP(grid);
print("从左上角到右下角的做小路径和为 $res");
//
//
res = minPathSumDPComp(grid);
print("从左上角到右下角的做小路径和为 $res");
}

View File

@ -26,7 +26,7 @@ int unboundedKnapsackDP(List<int> wgt, List<int> val, int cap) {
return dp[n][cap];
}
/* 完全背包:状态压缩后的动态规划 */
/* 完全背包:空间优化后的动态规划 */
int unboundedKnapsackDPComp(List<int> wgt, List<int> val, int cap) {
int n = wgt.length;
// dp
@ -56,7 +56,7 @@ void main() {
int res = unboundedKnapsackDP(wgt, val, cap);
print("不超过背包容量的最大物品价值为 $res");
//
//
int resComp = unboundedKnapsackDPComp(wgt, val, cap);
print("不超过背包容量的最大物品价值为 $resComp");
}

View File

@ -21,7 +21,7 @@ func climbingStairsDP(n int) int {
return dp[n]
}
/* 爬楼梯:状态压缩后的动态规划 */
/* 爬楼梯:空间优化后的动态规划 */
func climbingStairsDPComp(n int) int {
if n == 1 || n == 2 {
return n

View File

@ -31,7 +31,7 @@ func coinChangeIIDP(coins []int, amt int) int {
return dp[n][amt]
}
/* 零钱兑换 II状态压缩后的动态规划 */
/* 零钱兑换 II空间优化后的动态规划 */
func coinChangeIIDPComp(coins []int, amt int) int {
n := len(coins)
// 初始化 dp 表

View File

@ -17,7 +17,7 @@ func TestCoinChange(t *testing.T) {
res := coinChangeDP(coins, amt)
fmt.Printf("凑到目标金额所需的最少硬币数量为 %d\n", res)
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = coinChangeDPComp(coins, amt)
fmt.Printf("凑到目标金额所需的最少硬币数量为 %d\n", res)
}

View File

@ -91,7 +91,7 @@ func editDistanceDP(s string, t string) int {
return dp[n][m]
}
/* 编辑距离:状态压缩后的动态规划 */
/* 编辑距离:空间优化后的动态规划 */
func editDistanceDPComp(s string, t string) int {
n := len(s)
m := len(t)

View File

@ -34,7 +34,7 @@ func TestEditDistanceDFS(test *testing.T) {
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)
}

View File

@ -68,7 +68,7 @@ func knapsackDP(wgt, val []int, cap int) int {
return dp[n][cap]
}
/* 0-1 背包:状态压缩后的动态规划 */
/* 0-1 背包:空间优化后的动态规划 */
func knapsackDPComp(wgt, val []int, cap int) int {
n := len(wgt)
// 初始化 dp 表

View File

@ -34,7 +34,7 @@ func TestKnapsack(t *testing.T) {
res = knapsackDP(wgt, val, c)
fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res)
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = knapsackDPComp(wgt, val, c)
fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res)
}
@ -48,7 +48,7 @@ func TestUnboundedKnapsack(t *testing.T) {
res := unboundedKnapsackDP(wgt, val, c)
fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res)
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = unboundedKnapsackDPComp(wgt, val, c)
fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res)
}

View File

@ -24,7 +24,7 @@ func minCostClimbingStairsDP(cost []int) int {
return dp[n]
}
/* 爬楼梯最小代价:状态压缩后的动态规划 */
/* 爬楼梯最小代价:空间优化后的动态规划 */
func minCostClimbingStairsDPComp(cost []int) int {
n := len(cost) - 1
if n == 1 || n == 2 {

View File

@ -71,7 +71,7 @@ func minPathSumDP(grid [][]int) int {
return dp[n-1][m-1]
}
/* 最小路径和:状态压缩后的动态规划 */
/* 最小路径和:空间优化后的动态规划 */
func minPathSumDPComp(grid [][]int) int {
n, m := len(grid), len(grid[0])
// 初始化 dp 表

View File

@ -37,7 +37,7 @@ func TestMinPathSum(t *testing.T) {
res = minPathSumDP(grid)
fmt.Printf("从左上角到右下角的做小路径和为 %d\n", res)
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = minPathSumDPComp(grid)
fmt.Printf("从左上角到右下角的做小路径和为 %d\n", res)
}

View File

@ -29,7 +29,7 @@ func unboundedKnapsackDP(wgt, val []int, cap int) int {
return dp[n][cap]
}
/* 完全背包:状态压缩后的动态规划 */
/* 完全背包:空间优化后的动态规划 */
func unboundedKnapsackDPComp(wgt, val []int, cap int) int {
n := len(wgt)
// 初始化 dp 表

View File

@ -23,7 +23,7 @@ public class climbing_stairs_dp {
return dp[n];
}
/* 爬楼梯:状态压缩后的动态规划 */
/* 爬楼梯:空间优化后的动态规划 */
public static int climbingStairsDPComp(int n) {
if (n == 1 || n == 2)
return n;

View File

@ -34,7 +34,7 @@ public class coin_change {
return dp[n][amt] != MAX ? dp[n][amt] : -1;
}
/* 零钱兑换:状态压缩后的动态规划 */
/* 零钱兑换:空间优化后的动态规划 */
static int coinChangeDPComp(int[] coins, int amt) {
int n = coins.length;
int MAX = amt + 1;
@ -65,7 +65,7 @@ public class coin_change {
int res = coinChangeDP(coins, amt);
System.out.println("凑到目标金额所需的最少硬币数量为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = coinChangeDPComp(coins, amt);
System.out.println("凑到目标金额所需的最少硬币数量为 " + res);
}

View File

@ -31,7 +31,7 @@ public class coin_change_ii {
return dp[n][amt];
}
/* 零钱兑换 II状态压缩后的动态规划 */
/* 零钱兑换 II空间优化后的动态规划 */
static int coinChangeIIDPComp(int[] coins, int amt) {
int n = coins.length;
// 初始化 dp
@ -60,7 +60,7 @@ public class coin_change_ii {
int res = coinChangeIIDP(coins, amt);
System.out.println("凑出目标金额的硬币组合数量为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = coinChangeIIDPComp(coins, amt);
System.out.println("凑出目标金额的硬币组合数量为 " + res);
}

View File

@ -83,7 +83,7 @@ public class edit_distance {
return dp[n][m];
}
/* 编辑距离:状态压缩后的动态规划 */
/* 编辑距离:空间优化后的动态规划 */
static int editDistanceDPComp(String s, String t) {
int n = s.length(), m = t.length();
int[] dp = new int[m + 1];
@ -132,7 +132,7 @@ public class edit_distance {
res = editDistanceDP(s, t);
System.out.println("" + s + " 更改为 " + t + " 最少需要编辑 " + res + "");
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = editDistanceDPComp(s, t);
System.out.println("" + s + " 更改为 " + t + " 最少需要编辑 " + res + "");
}

View File

@ -69,7 +69,7 @@ public class knapsack {
return dp[n][cap];
}
/* 0-1 背包:状态压缩后的动态规划 */
/* 0-1 背包:空间优化后的动态规划 */
static int knapsackDPComp(int[] wgt, int[] val, int cap) {
int n = wgt.length;
// 初始化 dp
@ -109,7 +109,7 @@ public class knapsack {
res = knapsackDP(wgt, val, cap);
System.out.println("不超过背包容量的最大物品价值为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = knapsackDPComp(wgt, val, cap);
System.out.println("不超过背包容量的最大物品价值为 " + res);
}

View File

@ -26,7 +26,7 @@ public class min_cost_climbing_stairs_dp {
return dp[n];
}
/* 爬楼梯最小代价:状态压缩后的动态规划 */
/* 爬楼梯最小代价:空间优化后的动态规划 */
public static int minCostClimbingStairsDPComp(int[] cost) {
int n = cost.length - 1;
if (n == 1 || n == 2)

View File

@ -71,7 +71,7 @@ public class min_path_sum {
return dp[n - 1][m - 1];
}
/* 最小路径和:状态压缩后的动态规划 */
/* 最小路径和:空间优化后的动态规划 */
static int minPathSumDPComp(int[][] grid) {
int n = grid.length, m = grid[0].length;
// 初始化 dp
@ -118,7 +118,7 @@ public class min_path_sum {
res = minPathSumDP(grid);
System.out.println("从左上角到右下角的做小路径和为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = minPathSumDPComp(grid);
System.out.println("从左上角到右下角的做小路径和为 " + res);
}

View File

@ -27,7 +27,7 @@ public class unbounded_knapsack {
return dp[n][cap];
}
/* 完全背包:状态压缩后的动态规划 */
/* 完全背包:空间优化后的动态规划 */
static int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {
int n = wgt.length;
// 初始化 dp
@ -56,7 +56,7 @@ public class unbounded_knapsack {
int res = unboundedKnapsackDP(wgt, val, cap);
System.out.println("不超过背包容量的最大物品价值为 " + res);
// 状态压缩后的动态规划
// 空间优化后的动态规划
res = unboundedKnapsackDPComp(wgt, val, cap);
System.out.println("不超过背包容量的最大物品价值为 " + res);
}

View File

@ -19,7 +19,7 @@ function climbingStairsDP(n) {
return dp[n];
}
/* 爬楼梯:状态压缩后的动态规划 */
/* 爬楼梯:空间优化后的动态规划 */
function climbingStairsDPComp(n) {
if (n === 1 || n === 2) return n;
let a = 1,

View File

@ -20,7 +20,7 @@ def climbing_stairs_dp(n: int) -> int:
def climbing_stairs_dp_comp(n: int) -> int:
"""爬楼梯:状态压缩后的动态规划"""
"""爬楼梯:空间优化后的动态规划"""
if n == 1 or n == 2:
return n
a, b = 1, 2

View File

@ -27,7 +27,7 @@ def coin_change_dp(coins: list[int], amt: int) -> int:
def coin_change_dp_comp(coins: list[int], amt: int) -> int:
"""零钱兑换:状态压缩后的动态规划"""
"""零钱兑换:空间优化后的动态规划"""
n = len(coins)
MAX = amt + 1
# 初始化 dp 表
@ -55,6 +55,6 @@ if __name__ == "__main__":
res = coin_change_dp(coins, amt)
print(f"凑到目标金额所需的最少硬币数量为 {res}")
# 状态压缩后的动态规划
# 空间优化后的动态规划
res = coin_change_dp_comp(coins, amt)
print(f"凑到目标金额所需的最少硬币数量为 {res}")

View File

@ -26,7 +26,7 @@ def coin_change_ii_dp(coins: list[int], amt: int) -> int:
def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int:
"""零钱兑换 II状态压缩后的动态规划"""
"""零钱兑换 II空间优化后的动态规划"""
n = len(coins)
# 初始化 dp 表
dp = [0] * (amt + 1)
@ -54,6 +54,6 @@ if __name__ == "__main__":
res = coin_change_ii_dp(coins, amt)
print(f"凑出目标金额的硬币组合数量为 {res}")
# 状态压缩后的动态规划
# 空间优化后的动态规划
res = coin_change_ii_dp_comp(coins, amt)
print(f"凑出目标金额的硬币组合数量为 {res}")

View File

@ -75,7 +75,7 @@ def edit_distance_dp(s: str, t: str) -> int:
def edit_distance_dp_comp(s: str, t: str) -> int:
"""编辑距离:状态压缩后的动态规划"""
"""编辑距离:空间优化后的动态规划"""
n, m = len(s), len(t)
dp = [0] * (m + 1)
# 状态转移:首行
@ -118,6 +118,6 @@ if __name__ == "__main__":
res = edit_distance_dp(s, t)
print(f"{s} 更改为 {t} 最少需要编辑 {res}")
# 状态压缩后的动态规划
# 空间优化后的动态规划
res = edit_distance_dp_comp(s, t)
print(f"{s} 更改为 {t} 最少需要编辑 {res}")

View File

@ -59,7 +59,7 @@ def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:
def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:
"""0-1 背包:状态压缩后的动态规划"""
"""0-1 背包:空间优化后的动态规划"""
n = len(wgt)
# 初始化 dp 表
dp = [0] * (cap + 1)
@ -96,6 +96,6 @@ if __name__ == "__main__":
res = knapsack_dp(wgt, val, cap)
print(f"不超过背包容量的最大物品价值为 {res}")
# 状态压缩后的动态规划
# 空间优化后的动态规划
res = knapsack_dp_comp(wgt, val, cap)
print(f"不超过背包容量的最大物品价值为 {res}")

View File

@ -21,7 +21,7 @@ def min_cost_climbing_stairs_dp(cost: list[int]) -> int:
def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int:
"""爬楼梯最小代价:状态压缩后的动态规划"""
"""爬楼梯最小代价:空间优化后的动态规划"""
n = len(cost) - 1
if n == 1 or n == 2:
return cost[n]

View File

@ -63,7 +63,7 @@ def min_path_sum_dp(grid: list[list[int]]) -> int:
def min_path_sum_dp_comp(grid: list[list[int]]) -> int:
"""最小路径和:状态压缩后的动态规划"""
"""最小路径和:空间优化后的动态规划"""
n, m = len(grid), len(grid[0])
# 初始化 dp 表
dp = [0] * m
@ -99,6 +99,6 @@ if __name__ == "__main__":
res = min_path_sum_dp(grid)
print(f"从左上角到右下角的做小路径和为 {res}")
# 状态压缩后的动态规划
# 空间优化后的动态规划
res = min_path_sum_dp_comp(grid)
print(f"从左上角到右下角的做小路径和为 {res}")

View File

@ -23,7 +23,7 @@ def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:
def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:
"""完全背包:状态压缩后的动态规划"""
"""完全背包:空间优化后的动态规划"""
n = len(wgt)
# 初始化 dp 表
dp = [0] * (cap + 1)
@ -50,6 +50,6 @@ if __name__ == "__main__":
res = unbounded_knapsack_dp(wgt, val, cap)
print(f"不超过背包容量的最大物品价值为 {res}")
# 状态压缩后的动态规划
# 空间优化后的动态规划
res = unbounded_knapsack_dp_comp(wgt, val, cap)
print(f"不超过背包容量的最大物品价值为 {res}")

View File

@ -20,7 +20,7 @@ fn climbing_stairs_dp(n: usize) -> i32 {
dp[n]
}
/* 爬楼梯:状态压缩后的动态规划 */
/* 爬楼梯:空间优化后的动态规划 */
fn climbing_stairs_dp_comp(n: usize) -> i32 {
if n == 1 || n == 2 { return n as i32; }
let (mut a, mut b) = (1, 2);

View File

@ -29,7 +29,7 @@ fn coin_change_dp(coins: &[i32], amt: usize) -> i32 {
if dp[n][amt] != max { return dp[n][amt] as i32; } else { -1 }
}
/* 零钱兑换:状态压缩后的动态规划 */
/* 零钱兑换:空间优化后的动态规划 */
fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 {
let n = coins.len();
let max = amt + 1;
@ -61,7 +61,7 @@ pub fn main() {
let res = coin_change_dp(&coins, amt);
println!("凑到目标金额所需的最少硬币数量为 {res}");
// 状态压缩后的动态规划
// 空间优化后的动态规划
let res = coin_change_dp_comp(&coins, amt);
println!("凑到目标金额所需的最少硬币数量为 {res}");
}

View File

@ -28,7 +28,7 @@ fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 {
dp[n][amt]
}
/* 零钱兑换 II状态压缩后的动态规划 */
/* 零钱兑换 II空间优化后的动态规划 */
fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 {
let n = coins.len();
// 初始化 dp 表
@ -58,7 +58,7 @@ pub fn main() {
let res = coin_change_ii_dp(&coins, amt);
println!("凑出目标金额的硬币组合数量为 {res}");
// 状态压缩后的动态规划
// 空间优化后的动态规划
let res = coin_change_dp_ii_comp(&coins, amt);
println!("凑出目标金额的硬币组合数量为 {res}");
}

View File

@ -73,7 +73,7 @@ fn edit_distance_dp(s: &str, t: &str) -> i32 {
dp[n][m]
}
/* 编辑距离:状态压缩后的动态规划 */
/* 编辑距离:空间优化后的动态规划 */
fn edit_distance_dp_comp(s: &str, t: &str) -> i32 {
let (n, m) = (s.len(), t.len());
let mut dp = vec![0; m + 1];
@ -124,7 +124,7 @@ pub fn main() {
let res = edit_distance_dp(s, t);
println!("{s} 更改为 {t} 最少需要编辑 {res}");
// 状态压缩后的动态规划
// 空间优化后的动态规划
let res = edit_distance_dp_comp(s, t);
println!("{s} 更改为 {t} 最少需要编辑 {res}");
}

View File

@ -63,7 +63,7 @@ fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {
dp[n][cap]
}
/* 0-1 背包:状态压缩后的动态规划 */
/* 0-1 背包:空间优化后的动态规划 */
fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {
let n = wgt.len();
// 初始化 dp 表
@ -104,7 +104,7 @@ pub fn main() {
let res = knapsack_dp(&wgt, &val, cap);
println!("不超过背包容量的最大物品价值为 {res}");
// 状态压缩后的动态规划
// 空间优化后的动态规划
let res = knapsack_dp_comp(&wgt, &val, cap);
println!("不超过背包容量的最大物品价值为 {res}");
}

View File

@ -22,7 +22,7 @@ fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 {
dp[n]
}
/* 爬楼梯最小代价:状态压缩后的动态规划 */
/* 爬楼梯最小代价:空间优化后的动态规划 */
fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 {
let n = cost.len() - 1;
if n == 1 || n == 2 { return cost[n] };

View File

@ -66,7 +66,7 @@ fn min_path_sum_dp(grid: &Vec<Vec<i32>>) -> i32 {
dp[n - 1][m - 1]
}
/* 最小路径和:状态压缩后的动态规划 */
/* 最小路径和:空间优化后的动态规划 */
fn min_path_sum_dp_comp(grid: &Vec<Vec<i32>>) -> i32 {
let (n, m) = (grid.len(), grid[0].len());
// 初始化 dp 表
@ -113,7 +113,7 @@ pub fn main() {
let res = min_path_sum_dp(&grid);
println!("从左上角到右下角的最小路径和为 {res}");
// 状态压缩后的动态规划
// 空间优化后的动态规划
let res = min_path_sum_dp_comp(&grid);
println!("从左上角到右下角的最小路径和为 {res}");
}

View File

@ -24,7 +24,7 @@ fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {
return dp[n][cap];
}
/* 完全背包:状态压缩后的动态规划 */
/* 完全背包:空间优化后的动态规划 */
fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {
let n = wgt.len();
// 初始化 dp 表
@ -54,7 +54,7 @@ pub fn main() {
let res = unbounded_knapsack_dp(&wgt, &val, cap);
println!("不超过背包容量的最大物品价值为 {res}");
// 状态压缩后的动态规划
// 空间优化后的动态规划
let res = unbounded_knapsack_dp_comp(&wgt, &val, cap);
println!("不超过背包容量的最大物品价值为 {res}");
}

View File

@ -21,7 +21,7 @@ func climbingStairsDP(n: Int) -> Int {
return dp[n]
}
/* */
/* */
func climbingStairsDPComp(n: Int) -> Int {
if n == 1 || n == 2 {
return n

View File

@ -29,7 +29,7 @@ func coinChangeDP(coins: [Int], amt: Int) -> Int {
return dp[n][amt] != MAX ? dp[n][amt] : -1
}
/* */
/* */
func coinChangeDPComp(coins: [Int], amt: Int) -> Int {
let n = coins.count
let MAX = amt + 1
@ -62,7 +62,7 @@ enum CoinChange {
var res = coinChangeDP(coins: coins, amt: amt)
print("凑到目标金额所需的最少硬币数量为 \(res)")
//
//
res = coinChangeDPComp(coins: coins, amt: amt)
print("凑到目标金额所需的最少硬币数量为 \(res)")
}

View File

@ -28,7 +28,7 @@ func coinChangeIIDP(coins: [Int], amt: Int) -> Int {
return dp[n][amt]
}
/* II */
/* II */
func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int {
let n = coins.count
// dp
@ -60,7 +60,7 @@ enum CoinChangeII {
var res = coinChangeIIDP(coins: coins, amt: amt)
print("凑出目标金额的硬币组合数量为 \(res)")
//
//
res = coinChangeIIDPComp(coins: coins, amt: amt)
print("凑出目标金额的硬币组合数量为 \(res)")
}

View File

@ -88,7 +88,7 @@ func editDistanceDP(s: String, t: String) -> Int {
return dp[n][m]
}
/* */
/* */
func editDistanceDPComp(s: String, t: String) -> Int {
let n = s.utf8CString.count
let m = t.utf8CString.count
@ -140,7 +140,7 @@ enum EditDistance {
res = editDistanceDP(s: s, t: t)
print("\(s) 更改为 \(t) 最少需要编辑 \(res)")
//
//
res = editDistanceDPComp(s: s, t: t)
print("\(s) 更改为 \(t) 最少需要编辑 \(res)")
}

View File

@ -63,7 +63,7 @@ func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {
return dp[n][cap]
}
/* 0-1 */
/* 0-1 */
func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {
let n = wgt.count
// dp
@ -103,7 +103,7 @@ enum Knapsack {
res = knapsackDP(wgt: wgt, val: val, cap: cap)
print("不超过背包容量的最大物品价值为 \(res)")
//
//
res = knapsackDPComp(wgt: wgt, val: val, cap: cap)
print("不超过背包容量的最大物品价值为 \(res)")
}

View File

@ -22,7 +22,7 @@ func minCostClimbingStairsDP(cost: [Int]) -> Int {
return dp[n]
}
/* */
/* */
func minCostClimbingStairsDPComp(cost: [Int]) -> Int {
let n = cost.count - 1
if n == 1 || n == 2 {

View File

@ -67,7 +67,7 @@ func minPathSumDP(grid: [[Int]]) -> Int {
return dp[n - 1][m - 1]
}
/* */
/* */
func minPathSumDPComp(grid: [[Int]]) -> Int {
let n = grid.count
let m = grid[0].count
@ -116,7 +116,7 @@ enum MinPathSum {
res = minPathSumDP(grid: grid)
print("从左上角到右下角的做小路径和为 \(res)")
//
//
res = minPathSumDPComp(grid: grid)
print("从左上角到右下角的做小路径和为 \(res)")
}

View File

@ -24,7 +24,7 @@ func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {
return dp[n][cap]
}
/* */
/* */
func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {
let n = wgt.count
// dp
@ -56,7 +56,7 @@ enum UnboundedKnapsack {
var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap)
print("不超过背包容量的最大物品价值为 \(res)")
//
//
res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap)
print("不超过背包容量的最大物品价值为 \(res)")
}

View File

@ -19,7 +19,7 @@ function climbingStairsDP(n: number): number {
return dp[n];
}
/* 爬楼梯:状态压缩后的动态规划 */
/* 爬楼梯:空间优化后的动态规划 */
function climbingStairsDPComp(n: number): number {
if (n === 1 || n === 2) return n;
let a = 1,

View File

@ -22,7 +22,7 @@ fn climbingStairsDP(comptime n: usize) i32 {
return dp[n];
}
//
//
fn climbingStairsDPComp(comptime n: usize) i32 {
if (n == 1 or n == 2) {
return @intCast(n);

View File

@ -33,7 +33,7 @@ fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 {
}
}
//
//
fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 {
comptime var n = coins.len;
comptime var max = amt + 1;
@ -69,7 +69,7 @@ pub fn main() !void {
var res = coinChangeDP(&coins, amt);
std.debug.print("凑到目标金额所需的最少硬币数量为 {}\n", .{res});
//
//
res = coinChangeDPComp(&coins, amt);
std.debug.print("凑到目标金额所需的最少硬币数量为 {}\n", .{res});

View File

@ -28,7 +28,7 @@ fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 {
return dp[n][amt];
}
// II
// II
fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 {
comptime var n = coins.len;
// dp
@ -58,7 +58,7 @@ pub fn main() !void {
var res = coinChangeIIDP(&coins, amt);
std.debug.print("凑出目标金额的硬币组合数量为 {}\n", .{res});
//
//
res = coinChangeIIDPComp(&coins, amt);
std.debug.print("凑出目标金额的硬币组合数量为 {}\n", .{res});

View File

@ -88,7 +88,7 @@ fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 {
return dp[n][m];
}
//
//
fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 {
comptime var n = s.len;
comptime var m = t.len;
@ -138,7 +138,7 @@ pub fn main() !void {
res = editDistanceDP(s, t);
std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res });
//
//
res = editDistanceDPComp(s, t);
std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res });

View File

@ -63,7 +63,7 @@ fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 {
return dp[n][cap];
}
// 0-1
// 0-1
fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 {
var n = wgt.len;
// dp
@ -102,7 +102,7 @@ pub fn main() !void {
res = knapsackDP(&wgt, &val, cap);
std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res});
//
//
res = knapsackDPComp(&wgt, &val, cap);
std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res});

View File

@ -22,7 +22,7 @@ fn minCostClimbingStairsDP(comptime cost: []i32) i32 {
return dp[n];
}
//
//
fn minCostClimbingStairsDPComp(cost: []i32) i32 {
var n = cost.len - 1;
if (n == 1 or n == 2) {

View File

@ -68,7 +68,7 @@ fn minPathSumDP(comptime grid: anytype) i32 {
return dp[n - 1][m - 1];
}
//
//
fn minPathSumDPComp(comptime grid: anytype) i32 {
comptime var n = grid.len;
comptime var m = grid[0].len;
@ -114,7 +114,7 @@ pub fn main() !void {
res = minPathSumDP(&grid);
std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res});
//
//
res = minPathSumDPComp(&grid);
std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res});

View File

@ -24,7 +24,7 @@ fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32
return dp[n][cap];
}
//
//
fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 {
comptime var n = wgt.len;
// dp
@ -54,7 +54,7 @@ pub fn main() !void {
var res = unboundedKnapsackDP(&wgt, &val, cap);
std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res});
//
//
res = unboundedKnapsackDPComp(&wgt, &val, cap);
std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res});

View File

@ -110,7 +110,7 @@ $$
![爬楼梯最小代价的动态规划过程](dp_problem_features.assets/min_cost_cs_dp.png)
本题也可以进行状态压缩,将一维压缩至零维,使得空间复杂度从 $O(n)$ 降低至 $O(1)$ 。
本题也可以进行空间优化,将一维压缩至零维,使得空间复杂度从 $O(n)$ 降低至 $O(1)$ 。
=== "Java"

View File

@ -374,7 +374,7 @@ $$
=== "<12>"
![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png)
### 状态压缩
### 空间优化
由于每个格子只与其左边和上边的格子有关,因此我们可以只用一个单行数组来实现 $dp$ 表。

View File

@ -186,7 +186,7 @@ $$
=== "<15>"
![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png)
### 状态压缩
### 空间优化
由于 $dp[i,j]$ 是由上方 $dp[i-1, j]$ 、左方 $dp[i, j-1]$ 、左上方状态 $dp[i-1, j-1]$ 转移而来,而正序遍历会丢失左上方 $dp[i-1, j-1]$ ,倒序遍历无法提前构建 $dp[i, j-1]$ ,因此两种遍历顺序都不可取。

View File

@ -442,7 +442,7 @@ $$
- 将最小子问题对应的状态(即第 $1$ , $2$ 阶楼梯)称为「初始状态」。
- 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为「状态转移方程」。
## 状态压缩
## 空间优化
细心的你可能发现,**由于 $dp[i]$ 只与 $dp[i-1]$ 和 $dp[i-2]$ 有关,因此我们无须使用一个数组 `dp` 来存储所有子问题的解**,而只需两个变量滚动前进即可。
@ -520,4 +520,4 @@ $$
观察以上代码,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降低至 $O(1)$ 。
**这种空间优化技巧被称为「状态压缩」**。常见的动态规划问题中,当前状态仅与前面有限个状态有关,这时我们可以应用状态压缩,只保留必要的状态,通过“降维”来节省内存空间。
在动态规划问题中,当前状态往往仅与前面有限个状态有关,这时我们可以只保留必要的状态,通过“降维”来节省内存空间。**这种空间优化技巧被称为“滚动变量”或“滚动数组”**。

View File

@ -336,11 +336,11 @@ $$
=== "<14>"
![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png)
### 状态压缩
### 空间优化
由于每个状态都只与其上一行的状态有关,因此我们可以使用两个数组滚动前进,将空间复杂度从 $O(n^2)$ 将低至 $O(n)$ 。
进一步思考,我们是否可以仅用一个数组实现状态压缩呢?观察可知,每个状态都是由正上方或左上方的格子转移过来的。假设只有一个数组,当开始遍历第 $i$ 行时,该数组存储的仍然是第 $i-1$ 行的状态。
进一步思考,我们是否可以仅用一个数组实现空间优化呢?观察可知,每个状态都是由正上方或左上方的格子转移过来的。假设只有一个数组,当开始遍历第 $i$ 行时,该数组存储的仍然是第 $i-1$ 行的状态。
- 如果采取正序遍历,那么遍历到 $dp[i, j]$ 时,左上方 $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ 值可能已经被覆盖,此时就无法得到正确的状态转移结果。
- 如果采取倒序遍历,则不会发生覆盖问题,状态转移可以正确进行。
@ -348,7 +348,7 @@ $$
下图展示了在单个数组下从第 $i = 1$ 行转换至第 $i = 2$ 行的过程。请思考正序遍历和倒序遍历的区别。
=== "<1>"
![0-1 背包的状态压缩后的动态规划过程](knapsack_problem.assets/knapsack_dp_comp_step1.png)
![0-1 背包的空间优化后的动态规划过程](knapsack_problem.assets/knapsack_dp_comp_step1.png)
=== "<2>"
![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png)

View File

@ -11,8 +11,8 @@
**背包问题**
- 背包问题是最典型的动态规划题目,具有 0-1 背包、完全背包、多重背包等变种问题。
- 0-1 背包的状态定义为前 $i$ 个物品在剩余容量为 $c$ 的背包中的最大价值。根据不放入背包和放入背包两种决策,可得到最优子结构,并构建出状态转移方程。在状态压缩中,由于每个状态依赖正上方和左上方的状态,因此需要倒序遍历列表,避免左上方状态被覆盖。
- 完全背包的每种物品的选取数量无限制,因此选择放入物品的状态转移与 0-1 背包不同。由于状态依赖于正上方和正左方的状态,因此在状态压缩中应当正序遍历。
- 0-1 背包的状态定义为前 $i$ 个物品在剩余容量为 $c$ 的背包中的最大价值。根据不放入背包和放入背包两种决策,可得到最优子结构,并构建出状态转移方程。在空间优化中,由于每个状态依赖正上方和左上方的状态,因此需要倒序遍历列表,避免左上方状态被覆盖。
- 完全背包的每种物品的选取数量无限制,因此选择放入物品的状态转移与 0-1 背包不同。由于状态依赖于正上方和正左方的状态,因此在空间优化中应当正序遍历。
- 零钱兑换问题是完全背包的一个变种。它从求“最大”价值变为求“最小”硬币数量,因此状态转移方程中的 $\max()$ 应改为 $\min()$ 。从求“不超过”背包容量到求“恰好”凑出目标金额,因此使用 $amt + 1$ 来表示“无法凑出目标金额”的无效解。
- 零钱兑换 II 问题从求“最少硬币数量”改为求“硬币组合数量”,状态转移方程相应地从 $\min()$ 改为求和运算符。
@ -20,4 +20,4 @@
- 编辑距离Levenshtein 距离)用于衡量两个字符串之间的相似度,其定义为从一个字符串到另一个字符串的最小编辑步数,编辑操作包括添加、删除、替换。
- 编辑距离问题的状态定义为将 $s$ 的前 $i$ 个字符更改为 $t$ 的前 $j$ 个字符所需的最少编辑步数。当 $s[i] \ne t[j]$ 时,具有三种决策:添加、删除、替换,它们都有相应的剩余子问题。据此便可以找出最优子结构与构建状态转移方程。而当 $s[i] = t[j]$ 时,无须编辑当前字符。
- 在编辑距离中,状态依赖于其正上方、正左方、左上方的状态,因此状态压缩后正序或倒序遍历都无法正确地进行状态转移。为此,我们利用一个变量暂存左上方状态,从而转化到与完全背包等价的情况,可以在状态压缩后进行正序遍历。
- 在编辑距离中,状态依赖于其正上方、正左方、左上方的状态,因此空间优化后正序或倒序遍历都无法正确地进行状态转移。为此,我们利用一个变量暂存左上方状态,从而转化到与完全背包等价的情况,可以在空间优化后进行正序遍历。

View File

@ -104,14 +104,14 @@ $$
[class]{}-[func]{unbounded_knapsack_dp}
```
### 状态压缩
### 空间优化
由于当前状态是从左边和上边的状态转移而来,**因此状态压缩后应该对 $dp$ 表中的每一行采取正序遍历**。
由于当前状态是从左边和上边的状态转移而来,**因此空间优化后应该对 $dp$ 表中的每一行采取正序遍历**。
这个遍历顺序与 0-1 背包正好相反。请借助下图来理解两者的区别。
=== "<1>"
![完全背包的状态压缩后的动态规划过程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png)
![完全背包的空间优化后的动态规划过程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png)
=== "<2>"
![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png)
@ -370,9 +370,9 @@ $$
=== "<15>"
![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png)
### 状态压缩
### 空间优化
零钱兑换的状态压缩的处理方式和完全背包一致。
零钱兑换的空间优化的处理方式和完全背包一致。
=== "Java"
@ -540,9 +540,9 @@ $$
[class]{}-[func]{coin_change_ii_dp}
```
### 状态压缩
### 空间优化
状态压缩处理方式相同,删除硬币维度即可。
空间优化处理方式相同,删除硬币维度即可。
=== "Java"

View File

@ -2,17 +2,25 @@
在某些情况下,我们希望使用一个列表的所有元素来构建一个堆,这个过程被称为“建堆操作”。
## 借助入堆方法实现
## 自上而下构建
最直接的方法是借助“元素入堆操作”实现。我们首先创建一个空堆,然后将列表元素依次执行“入堆”
我们首先创建一个空堆,然后遍历列表,依次对每个元素执行“入堆操作”,即先将元素添加至堆的尾部,再对该元素执行“从底至顶”堆化
设元素数量为 $n$ ,入堆操作使用 $O(\log{n})$ 时间,因此将所有元素入堆的时间复杂度为 $O(n \log n)$
每当一个元素入堆,堆的长度就加一,因此堆是“自上而下”地构建的
## 基于堆化操作实现
设元素数量为 $n$ ,每个元素的入堆操作使用 $O(\log{n})$ 时间,因此该建堆方法的时间复杂度为 $O(n \log n)$ 。
有趣的是,存在一种更高效的建堆方法,其时间复杂度可以达到 $O(n)$ 。我们先将列表所有元素原封不动添加到堆中,然后倒序遍历该堆,依次对每个节点执行“从顶至底堆化”。
## 自下而上构建
请注意,因为叶节点没有子节点,所以无须堆化。在代码实现中,我们从最后一个节点的父节点开始进行堆化。
实际上,我们可以实现一种更为高效的建堆方法,共分为两步。
1. 将列表所有元素原封不动添加到堆中。
2. 倒序遍历堆(即层序遍历的倒序),依次对每个非叶节点执行“从顶至底堆化”。
在倒序遍历中,堆是“自下而上”地构建的,需要重点理解以下两点。
- 由于叶节点没有子节点,因此无需对它们执行堆化。最后一个节点的父节点是最后一个非叶节点。
- 在倒序遍历中,我们能够保证当前节点之下的子树已经完成堆化(已经是合法的堆),而这是堆化当前节点的前置条件。
=== "Java"
@ -88,24 +96,24 @@
## 复杂度分析
为什么第二种建堆方法的时间复杂度是 $O(n)$ ?我们来展开推算一下
下面,我们来尝试推算第二种建堆方法的时间复杂度
- 在完全二叉树中,设节点总数为 $n$ ,则叶节点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此,在排除叶节点后,需要堆化的节点数量为 $(n - 1)/2$ ,复杂度为 $O(n)$ 。
- 在从顶至底堆化的过程中,每个节点最多堆化到叶节点,因此最大迭代次数为二叉树高度 $O(\log n)$ 。
- 假设完全二叉树的节点数量为 $n$ ,则叶节点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此需要堆化的节点数量为 $(n - 1) / 2$ 。
- 在从顶至底堆化的过程中,每个节点最多堆化到叶节点,因此最大迭代次数为二叉树高度 $\log n$ 。
将上述两者相乘,可得到建堆过程的时间复杂度为 $O(n \log n)$ 。**然而,这个估算结果并不准确,因为我们没有考虑到二叉树底层节点数量远多于顶层节点的性**。
将上述两者相乘,可得到建堆过程的时间复杂度为 $O(n \log n)$ 。**这个估算结果并不准确,因为我们没有考虑到二叉树底层节点数量远多于顶层节点的性**。
接下来我们来进行更为详细的计算。为了减小计算难度,我们假设树是一个“完美二叉树”,该假设不会影响计算结果的正确性。设二叉树(即堆)节点数量为 $n$ ,树高度为 $h$ 。
接下来我们来进行更为准确的计算。为了减小计算难度,假设给定一个节点数量为 $n$ ,高度为 $h$ 的“完美二叉树”,该假设不会影响计算结果的正确性。
![完美二叉树的各层节点数量](build_heap.assets/heapify_operations_count.png)
如上图所示,**节点“从顶至底堆化”的最大迭代次数等于该节点到叶节点的距离,而该距离正是“节点高度”**。因此,我们可以将各层的“节点数量 $\times$ 节点高度”求和,**从而得到所有节点的堆化迭代次数的总和**。
如上图所示,节点“从顶至底堆化”的最大迭代次数等于该节点到叶节点的距离,而该距离正是“节点高度”。因此,我们可以将各层的“节点数量 $\times$ 节点高度”求和,**从而得到所有节点的堆化迭代次数的总和**。
$$
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1
$$
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,得到
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,得到
$$
\begin{aligned}
@ -114,13 +122,13 @@ T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline
\end{aligned}
$$
使用错位相减法,用下式 $2 T(h)$ 减去上式 $T(h)$ ,可得
使用错位相减法,用下式 $2 T(h)$ 减去上式 $T(h)$ ,可得
$$
2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h
$$
观察上式,发现 $T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为
观察上式,发现 $T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为
$$
\begin{aligned}