【算法】动态规划(背包问题,持续更新……)

文章介绍了背包问题的两种经典形式——0-1背包和完全背包,通过动态规划求解物品装入背包以达到最大价值或最小硬币数量。0-1背包中,每种物品仅有一件,而完全背包允许无限数量的同种物品。状态转移方程和优化策略,如一维数组的使用,以及正序或逆序枚举的注意事项,都在解题过程中起到关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背包问题

1.0-1背包问题

问题描述:
  有n件物品,每件物品的重量为w[i],价值为c[i],选取物品放进容量为V的背包,使得背包内物品总价值最大,每件物品只有1件。
解决方法:
  令dp[i][v]表示前i件物品恰好装入容量为V的背包所能获得的最大价值。
1.若不放第i件物品,转换为前i-1件物品放入v的最大价值,即dp[i-1][v]。
2.若放第i件物品,转为前i-1件物品放入v-w[i]的最大价值,即dp[i-1][v-w[i]]+c[i]。
状态转移方程:
  二维:dp[i][v]=max(dp[i-1][v], dp[i-1][v-w[i]]+c[i]) (w[i]=<v<=V)
注意到,dp[i][v]只和前一个状态i-1有关,因此可以把二维数组变换为一维数组:
  一维:dp[v]=max(dp[v], dp[v-w[i]]+c[i]) (w[i]=<v<=V)
使用二维方程时,v的枚举正序逆序都可以,而一维方程的枚举必须是逆序的(从大到小枚举),即

for(int i = 1; i <= n; i++) {
	for(int v = V; v >= w[i];v--) { // 逆序
		// 状态转移方程;
		// 如果是正序的话,dp[i][v]的更新涉及到dp[i-1][v-w[i]],由于是一维数组,dp[i-1][v-w[i]]已经被更新为dp[i][v-w[i]],不是我们想要的i-1时的状态,而逆序不会出现这个问题。
	}

例题:
494. 目标和
474. 一和零

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];

        for(int i = 0; i < strs.length; i++) {
            // 得到0、1数量
            int[] k = get_zero_one(strs[i]);
            for(int v0 = m; v0 >= k[0]; v0--) {
                for(int v1 = n; v1 >= k[1]; v1--) {
                    dp[v0][v1] = Math.max(dp[v0][v1], dp[v0-k[0]][v1-k[1]] + 1);
                }
            }
        }
        return dp[m][n];
    }
    int[] get_zero_one(String s) {
        int count[] = new int[2];
        for(int i = 0; i < s.length(); i++) {
            if(s.charAt(i) == '0') {
                count[0]++;
            }else {
                count[1]++;
            }
        }
        return count;
    }
}

2.完全背包问题

问题描述:
  有n件物品,每件物品的重量为w[i],价值为c[i],选取物品放进容量为V的背包,使得背包内物品总价值最大,每件物品有无数件
解决方法:
  令dp[i][v]表示前i件物品恰好装入容量为V的背包所能获得的最大价值。
1.若不放第i件物品,转换为前i-1件物品放入V的最大价值,即dp[i-1][v]。
2.若放第i件物品,由于可以取无数件物品,转为前i-1件物品放入V-w[i]的最大价值,即dp[i][V-w[i]]+c[i]。
状态转移方程:
  dp[i][v]=max(dp[i-1][v], dp[i][v-w[i]]+c[i]) (w[i] =<v<= V)
同样,可以改写为一维:
  dp[v] = max(dp[v],dp[v-w[i]]+c[i]) (w[i] =<v<= V)
这里v的枚举必须为正序。因为dp[i][v]和dp[i][V-w[i]]有关,必须先正序算出dp[i][v-w[i]]才能得到dp[i][v]。
例题:
494. 目标和
322. 零钱兑换

零钱兑换题解:
   思路和完全背包问题一样,只是dp定义为前i个硬币金额为v时所需的最少硬币数量,我们可以先定义二维数组dp[i][v]=min(dp[i-1][v], dp[i][v-w[i]]+1),不选当前硬币,状态为dp[i-1][v],选择当前硬币,硬币数量多1,状态为dp[i][v-w[i]]+1,取其中的最小值,同样,二维数组可以省略为一维数组,且正序枚举v。
注意点:取最小值因此dp数组初始化时要用一个较大值初始化,当总金额为0时,硬币数量可为0,因此dp[0]=0。

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1]; // 金额为v所需的最少硬币个数

        Arrays.fill(dp, 10010);
        dp[0] = 0; // 金额为0时最少为0
        for(int i = 0; i < coins.length; i++) {
            for(int v = coins[i]; v <= amount; v++) {
                dp[v] = Math.min(dp[v], dp[v-coins[i]] + 1);
            }
        }
        if(dp[amount] != 10010) {
            return dp[amount];
        }else {
            return -1;
        }
        
    }
}

正方形问题:
dp(i, j)表示以i,j为顶点的最大正方形xx数。
221. 最大正方形
1277. 统计全为 1 的正方形子矩阵
1139. 最大的以 1 为边界的正方形
数组问题:
152. 乘积最大子数组

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值