背包dp以及空间压缩

01背包问题

  1. 按照状态找:
    dp[i][w] 表示对于前i个物品,容量为w的背包的最大价值
    dp[i][w] = max(dp[i-1][w], dp[i-1][w-wt[i-1]] + val[i-1]);
  2. 到达当前dp[i][w]状态的情况综合,放入第i个物品,或者不放第i物品
  3. 先凑答案就是dp[N][W]的情况,这时候dp的下标是比单个val和wt的下标大一个的,因为前者是size,后者是下标
int knapsack(int W, int N, vector<int>& wt, vector<int>& val) {
    vector<vector<int>> dp(N+1,vector<int>(W+1,0));
    for(int i=1; i<=N; i++){
    	for(int w=1; w<=W; w++){
    	   // 前置条件 w-wt[i-1]不能小于0,也就是存不下第i个,那只能选不存第i个的情况
    	   if (w - wt[i-1] <0){
    	   	dp[i][w] = dp[i-1][w];
    	   }
    	   dp[i][w] = max(dp[i-1][w], 
    	   		  dp[i-1][w-wt[i-1]] + val[i-1]);
    	}
    }
    return dp[N][W];
}

完全背包问题

  1. 找状态 dp[i][j]表示使用前 i 种物品,填满 j 容量背包有几种方法
  2. 状态转移:全存前 i-1;加入第 i 种,那要看之前缺 i 元的情况数
    dp[i][j] = dp[i-1][j]+dp[i][j-coins[i-1]]
class Solution {
public:
int change(int amount,vector<int>& coins){
	int n =coins.size();
    vector<vector<int>> dp(n+1,vector<int>(amount+1));
	// base case
	for(int i=0; i<=n; i++){
		dp[i][0] = 1;
	}
		
	for(int i=1;i<=n;i++){
		for(int j=1;j<=amount;j++){
			// coins[i-1]不能大于j,否则往前推就小于0了,一个也存不下,只可能是等于取i-1中物品的情况数
			if(coins[i-1] > j){
				dp[i][j] = dp[i-1][j];
			}else {
				dp[i][j] = dp[i-1][j]+dp[i][j-coins[i-1]]; 
			}
		}
	}
	return dp[n][amount];
}
};

子集背包问题:分割等和子集

  1. 先看看总和是不是奇数,奇数不能划分
  2. 问题转化为求找到 和为sum/2的子串
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(auto num:nums) sum+=num;
        if(sum&1)   return false;
        sum /= 2;
        int n=nums.size();
        // 前i个数字,完全背包容量j
        vector<vector<int>> dp(n+1,vector<int>(sum+1,false));
        //base case
        for(int i=0;i<=n;i++){
            dp[i][0] = true;
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=sum;j++){
                if(j < nums[i-1]){
                    dp[i][j]=dp[i-1][j];
                }else{
                    dp[i][j]= dp[i-1][j] || dp[i-1][j-nums[i-1]];//这里不同于完全背包问题,不能dp[i-1][j-coins[i-1]],因为新加的元素不是无限使用的。
                }   
            }
        }
       
        return dp[n][sum];
    }
};

dp数组空间压缩

  • 当dp是二维数组[i][j],但是状态转移只和上一次的一整条 j 长的数组已经当秋安元素的相邻元素有关,那可以把 维度 i 去掉,改为[j]。 然后去修改状态转移方程和 base case 的写法,其中最主要是考虑更新dp数组的时候可能把状态转移需要的一些元素给覆盖了。重点需要考虑的是:
  1. 空间压缩实际上是往第二个维度的轴上投影,按这种原则来修改状态转移方程和base case。
  2. 考虑遍历的方向,如果这一排在做状态转移,值依赖上一排的比 j 先前位置的元素,那应该 j 考虑从后往前dp。

比如说上面那个分割等和子集(值只依赖前一排,而且依赖前一排比 j 前面的元素):
原dp:

for(int i=1;i<=n;i++){
      for(int j=1;j<=sum;j++){
          if(j < nums[i-1]){
              dp[i][j]=dp[i-1][j];
           }else{
			  						// 这里要用到的前一排比dp[i-1][j]靠前的元素,所以为了不事先破坏这个考前的元素,dp空间压缩的时候需要从后往前遍历
              dp[i][j]= dp[i-1][j] || dp[i-1][j-nums[i-1]]
           }   
        }
}

空间压缩dp:

for(int i=1;i<=n;i++){
	for(int j=sum;j >= 0;j--){
		if(j >= nums[i-1]){
			// 这里要用到的前一排比dp[i-1][j]靠前的元素,所以为了不事先破坏这个考前的元素,dp空间压缩的时候需要从后往前遍历
			dp[j]= dp[j] || dp[j-nums[i-1]];
		}   
	}
}
  1. 如果状态转移的得到的值不仅依赖上一行,而且依赖本行邻接左右的值,那需要考虑覆盖问题。
    这种情况分为两种: 一种是本行依赖的在遍历方向的上游,那就是在上次状态转移的时候把本行依赖的先前的值用temp存起来,状态转换转移的时候用,在本格计算玩存。 另一种是在遍历方向的下游,那就要转变遍历方向,然后重新考虑上一种情况

比如说考虑这个4要结合3和2考虑,遍历方向是从下往上,从左往右,那就是第一种情况,存起来上游的那个值就行
|3|4|
|1|2|

 for (int i = n - 2; i >= 0; i--) {
        int pre = 0;
        for (int j = i + 1; j < n; j++) {
            int temp = dp[j];
            // 状态转移方程
            if (s[i] == s[j])
                dp[j] = pre + 2;
            else
                dp[j] = max(dp[j], dp[j - 1]);
            pre = temp;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值