0-1背包问题和完全背包问题

先总体介绍一下0-1背包和完全背包的区别

主要是01背包和完全背包(背包九讲里面的其他背包问题,都是竞赛级别的,leetcode上面没有)

01背包就是:背包存在一个最大容量V,每个物品有两个参数:体积w和价值v,目的是在不超过背包最大容量的前提下,取出物品的价值最大

如果每个物品的数量只有1个,那就是01背包问题

如果每个物品的数量是无限的,那就是完全背包问题

leetcode上没有纯01背包的问题,都是01背包应用方面的题目,也就是需要转化为01背包问题。

动态规划要搞清楚三个问题:

(1)dp[i][j]的含义

(2)状态转移方程 

(3) dp数组开多大,最后返回什么,一般是返回数组的最后一个元素

物体的数量是m(m个物体),背包可以装的最大容量为w,开一个dp[m+1][w+1]的二维数组

第i件商品的重量为weight[i-1],重量为values[i-1]

状态转移方程就是考虑第i个物品放不放的进去  

 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i-1]] + value[i-1]);
 前者是不拿第i个物品,后者是拿第i个物品(拿前i-1个物品的价值+第i个物品的价值就是拿前i个物品的价值,这里要主要第i个物品的重量是weight[i-1],价值是value[i-1])

代码实现:

传入三个参数,weight数组,value数组,bagweight(背包能装的最大重量)

物品有weight[]数组和values[]数组

背包的能装的最大重量为bageweight

public int  help(int[] weight,int[] value,int bagweight)
{
   int[][]  dp=new int[weight.length+1][bagweight+1];

  
   // weight数组的大小 就是物品个数
   for(int i = 1; i < weight.length+1; i++) // 遍历物品
   { 
       for(int j = 1; j < bagweight+1; j++) // 遍历背包容量
       {
           if (weight[i-1]>j) //如果光是第i个物品本身的重量已经超过了背包容量,这说明肯定不能取第i个物品,第i个物品对应的是weight[i-1]和value[j-1]
           {
               dp[i][j] = dp[i - 1][j]; 
           }
           else 
           {
             dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i-1]] + value[i-1]);
             //前者是不拿第i个物品,后者是拿第i个物品(拿前i-1个物品的价值+第i个物品的价值就是拿前i个物品的价值,这里要主要第i个物品的重量是weight[i-1],价值是value[i-1])
           }
        }
    }
    return   dp[weight.length][bagweight];
}

这两个问题都是横轴是可以选的数,纵轴是要拼成的target

不要想压缩到一维数组,掌握二维数组的解法就够了

这两种背包问题就是二维dp问题,把二维表格画出来基本就能解决了

力扣416  分割等和子集

这个问题就转化成,问你这个集合的子集中,是否存在一个子集,它的所有元素之和等于target

其中 dp[i] 表示是否可以找到一个子集,使得其元素和等于 i

step1:开二维dp数组:int[][] dp=new int[nums.length][],确定dp[i][j]的含义

 step2:第一行,只用nums[i]这一个数,肯定只能拼出nums[i],所以:

for(int j=0;j<=target;j++)
{
  if(j==nums[0])
  { 
     dp[0][j]=true;
  }
}


step3:状态转移方程

dp[i][j]表示从nums[0]-nums[i]中这些数中能否拼成j

(1)当nums[i]=j时,表示nums[i]一个数就可以拼出j,所以dp[i][j]一定是true

(2)如果nums[i]<j,表示nums[i]这个数小于j,那么dp[i][j]=dp[i-1][j-nums[i]],也就是说前面的数能拼成j-nums[j],那么加上nums[i]这个数,就能拼成j

(3)如果dp[i-1][j]=true,表示不用nums[i]这个数,也能拼出j,那么说明dp[i][j]一定是true

完整代码如下:

class Solution
{
    public boolean canPartition(int[] nums) 
    {
       if(nums.length==0)  return false;
       int  n=nums.length;
       int sum=0;
       for(int num:nums)
       {
           sum=sum+num;
       }
    
       //总和为奇数,那肯定是不能分成两个和相等的子集
       if(sum%2!=0)  return false;

       int target=sum/2;

       boolean[][] dp=new boolean[nums.length][target+1];

       for(int j=0;j<=target;j++)
       {
           if(j==nums[0])
           { 
              dp[0][j]=true;
           }
       }

       //从第一行开始,然后是第二行,第三行.....
       for(int i=1;i<nums.length;i++)
       {
           for(int j=0;j<=target;j++)
           {
               if(nums[i]==j)  dp[i][j]=true;
               if(nums[i]<j)  dp[i][j]=dp[i-1][j-nums[i]]||dp[i-1][j];
               if(nums[i>j])   dp[i][j]=dp[i-1][j];
           }
       }
       return  dp[nums.length-1][target];  
    }
}

完全背包问题:

public class Knapsack {
    public static int knapsack(int[] w, int[] v, int V) {
        int n = w.length;
        int[][] dp = new int[n + 1][V + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= V; j++) {
                for (int k = 0; k * w[i - 1] <= j; k++)//此时背包容量大小为j
                {
                    dp[i][j] = Math.max(dp[i-1][j], dp[i - 1][j - k * w[i - 1]] + k * v[i - 1]);
//前者是不加入第i件物品,后者是加入第i件物品(第i件物品的重量是weight[i-1],价值是value[i-1])
                }
            }
        }
        return dp[n][V];
    }
}

力扣322 零钱兑换(每种硬币的数量是无限的)

定义 dp[i][j]为考虑前 i件物品,凑成总和为 j 所需要的最少硬币数量

class Solution 
{
    public int coinChange(int[] coins, int amount) 
    {
        int n=coins.length,m=amount;
        int INF=Integer.MAX_VALUE;
        int[][]dp=new int[n+1][m+1];
        //初始化,amount=0
        for (int i = 1; i <= n ; i++)
        {
            dp[i][0]=0;
        }
        //初始化,coins=0
        for (int i = 1; i <= amount; i++)
        {
            dp[0][i]=INF;
        }
        for (int i = 1; i <=n ; i++)
        {
            for (int j = 1; j <=m ; j++) 
            {
                dp[i][j]=dp[i-1][j];
                for (int k = 1; k*coins[i-1]<=j ; k++)
                {
                      if(dp[i-1][j-k*coins[i-1]]!=INF)
                      {
                          dp[i][j]=Math.min(dp[i-1][j-k*coins[i-1]]+k,dp[i][j]);
                      }
                }
            }
        }
        return dp[n][m]==INF?-1:dp[n][m];
    }
}
class Solution {
    static int N = 0x3f3f3f3f;
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[][] f = new int[n + 1][amount + 1];
        for(int i = 0; i < n + 1; i ++){
            Arrays.fill(f[i], N);
        }
        for(int i = 0; i < n + 1; i ++) f[i][0] = 0;
        for(int i = 1; i <= n; i ++){
            for(int j = 0; j <= amount; j ++){
                int maxCount = j / coins[i - 1];
                for(int k = 0; k <= maxCount; k ++){
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - coins[i - 1] * k] + k);
                }
            }
        }
        if(f[n][amount] == N) return -1;
        else return f[n][amount];
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值