zoukankan      html  css  js  c++  java
  • 动态规划讲解

    https://tangshusen.me/2019/11/24/knapsack-problem/

     

    https://mp.weixin.qq.com/s?src=11&timestamp=1596210639&ver=2494&signature=ebLRWzj10GYY8W5wWUipHYsbdDQIO7q-NwsWE7QNR52R67ZrtYBrX7oaXp3bvDtR3xXCoqpJbAc9NooN0fijlMufdXxT-MhpIRfAm30g1Fek7gVDFGO1t6s0LuFYtWiV&new=1

     

    0/1背包

    //01背包问题伪代码(空间优化版)
    dp[0,...,W]=0;
    for i=1,...,N
       for j=W,...w[i]//必须逆向枚举!!!
           dp[j]=max(dp[j],dp[j-w[i]]+v[i])
           
    //完全背包问题思路伪代码(空间优化)
    dp[0,...,W]=0;
    for i=1,...,N
       for j=w[i],...,W//必须正向枚举!!!
           dp[j]=max(dp[j],dp[j-w[i]]+v[i])
    //恰好装满要注意初始化值,重量为0时最大价值为0,dp[0]=0;而当重量不为0时,暂时不知道最大价值,但是由于后面要max,所以初始化Integer.MIN_VALUE;
    //求方案数总和

     

    时间复杂度O(NW),空间复杂度O(W)

    package com.dj;


    import java.util.Arrays;

    public class test {
       // 所有的物品
       private Knapsack[] bags;
       // 物品的数量
       private int n;
       // 背包总承重
       private int totalWeight;
       // 第一维:当前第几个物品;第二维:当前的背包承重;值:当前背包最大价值
       private int[][] bestValues;
       private int[] res;
       // 最终背包中最大价值
       private int bestValue;

       public test(Knapsack[] bags, int totalWeight) {
           this.bags = bags;
           this.totalWeight = totalWeight;
           this.n = bags.length;
           if (bestValues == null) {
               // 考虑0的状态+1,防止数组角标越界
               bestValues = new int[n + 1][totalWeight + 1];
          }
           if(res==null){
               res=new int[totalWeight+1];
          }
      }
       public void solve2(){
           //对一维数组F进行初始化,即当背包不放入物品,其最大价值均为0
           for(int i=0;i<=totalWeight;i++){
               res[i]=0;
          }
           for(int i = 1; i <= n; i++){
               for(int j=totalWeight;j>=0;j--){
                   if(j>=bags[i-1].getWeight()){
                       res[j]=Math.max(res[j],res[j-bags[i-1].getWeight()]+bags[i-1].getValue());
                  }else{//可省略
                       res[j]=res[j];
                  }
              }
          }
           System.out.println(res[totalWeight]);
      }
       public void solve() {
           // 遍历背包的承重
           for (int j = 0; j <= totalWeight; j++) {
               // 遍历指定物品
               for (int i = 0; i <= n; i++) {
                   // 当背包不放入物品或承重为0时,其最大价值均为0
                   if (i == 0 || j == 0) {
                       bestValues[i][j] = 0;
                  } else {
                       // 如果第 i个物品重量大于总承重,则最优解存在于前 i-1 个背包中
                       if (j < bags[i - 1].getWeight()) {
                           bestValues[i][j] = bestValues[i - 1][j];
                      } else {
                           // 如果第 i个物品不大于总承重,则最优解要么是包含第 i个背包的最优解,
                           // 要么是不包含第 i个背包的最优解, 取两者最大值
                           int weight = bags[i - 1].getWeight();
                           int value = bags[i - 1].getValue();
                           bestValues[i][j] = Math.max(bestValues[i - 1][j], value
                                   + bestValues[i - 1][j - weight]);
                      }
                  }
              }
          }

           bestValue = bestValues[n][totalWeight];
      }

       public int getBestValue() {
           return bestValue;
      }

       public static void main(String[] args) {
           Knapsack[] bags = new Knapsack[] { new Knapsack(2, 13),
                   new Knapsack(1, 10), new Knapsack(3, 24), new Knapsack(2, 15),
                   new Knapsack(4, 28), new Knapsack(5, 33), new Knapsack(3, 20),
                   new Knapsack(1, 8) };
           int totalWeight = 12;
           test problem = new test(bags, totalWeight);
           problem.solve();
           System.out.println(problem.getBestValue());
           problem.solve2();
      }
    }

    class Knapsack{
       /** 物品重量 */
       private int weight;
       /** 物品价值 */
       private int value;

       public Knapsack(int weight, int value) {
           this.weight = weight;
           this.value = value;
      }

       public int getWeight() {
           return weight;
      }

       public void setWeight(int weight) {
           this.weight = weight;
      }

       public int getValue() {
           return value;
      }

       public void setValue(int value) {
           this.value = value;
      }

    }

    石头碰撞问题

    链接:https://www.nowcoder.com/questionTerminal/9dd19c9305704138bdf83e2dffdcb4f4?f=discussion 来源:牛客网

     

    给定一组石头,每个石头有一个正数的重量。每一轮开始的时候,选择两个石头一起碰撞,假定两个石头的重量为x,y,x<=y,碰撞结果为

    1. 如果x==y,碰撞结果为两个石头消失

    2. 如果x != y,碰撞结果两个石头消失,生成一个新的石头,新石头重量为y-x

      最终最多剩下一个石头为结束。求解最小的剩余石头质量的可能性是多少。

    输入描述:
    第一行输入石头个数(<=100)

    第二行输入石头质量,以空格分割,石头质量总和<=10000
    输出描述:
    最终的石头质量

    示例1

    输入
    6
    2 7 4 1 8 1
    输出
    1
    链接:https://www.nowcoder.com/questionTerminal/9dd19c9305704138bdf83e2dffdcb4f4?f=discussion
    来源:牛客网

    package com.dj;

    import java.util.*;


    public class test {

       public static void main(String[] args){
           Scanner sc = new Scanner(System.in);
           int n = sc.nextInt();
           sc.nextLine();
           int[] stones = new int[n] ;
           for(int i = 0; i < n;i++){
               stones[i] = sc.nextInt();
          }
           int ans = lastStoneWeightII(stones);
           System.out.println(ans);
           int res=helper(stones);
           System.out.println(res);
      }

       public static int helper(int[] stones){
           int len = stones.length;
           /* 获取石头总重量 */
           int totalStone = 0;
           for (int i : stones) {
               totalStone += i;
          }

           int maxCapacity = totalStone / 2;
           int[] dp = new int[totalStone+1];
           for(int i = 1; i < len + 1; i++){
               for (int j = maxCapacity; j >= 1; j--){
                   if (j - stones[i - 1] < 0){
                       dp[j]=dp[j];
                  }else{
                       dp[j]=Math.max(dp[j],dp[j-stones[i-1]]+stones[i-1]);
                  }
              }
          }
           return totalStone - 2 * dp[maxCapacity];
      }
       public static int lastStoneWeightII(int[] stones) {
           /*
            1.换一种想法,就是将这些数字分成两拨,使得他们的和的差最小
            2.因此可以简单地把所有石头看作两堆,假设总重量为 sum,
            则问题转化为背包问题:如何使两堆石头总重量接近 sum / 2
            3.两堆石头的价值(重量)都只能接近
            */
           int len = stones.length;
           /* 获取石头总重量 */
           int totalStone = 0;
           for (int i : stones) {
               totalStone += i;
          }

           int maxCapacity = totalStone / 2;
           /*
            1.这个是0-1背包的dp定义,dp[i][w]: 前i个商品,当容量为w时,最大价值为dp[i][w]
            2.对照一下此题dp数组定义,dp[i][j]: 前i个石头,当容量为j时,最大重量为dp[i][j]
           */
           int[][] dp = new int[len + 1][totalStone + 1];


           for (int i = 1; i < len + 1; i++) {
               for (int j = 1; j < maxCapacity + 1; j++) {
                   if (j - stones[i - 1] < 0) {
                       dp[i][j] = dp[i - 1][j];
                  } else {
                       dp[i][j] = Math.max(dp[i - 1][j - stones[i - 1]] + stones[i - 1], dp[i - 1][j]);
                  }
              }
          }
           return totalStone - 2 * dp[len][maxCapacity];

      }
    }

    二维01背包问题求最大价值

    474. 一和零

    难度中等185

    在计算机界中,我们总是追求用有限的资源获取最大的收益。

    现在,假设你分别支配着 m0n1。另外,还有一个仅包含 01 字符串的数组。

    你的任务是使用给定的 m0n1 ,找到能拼出存在于数组中的字符串的最大数量。每个 01 至多被使用一次

    注意:

    1. 给定 01 的数量都不会超过 100

    2. 给定字符串数组的长度不会超过 600

    示例 1:

    输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
    输出: 4

    解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。

    示例 2:

    输入: Array = {"10", "0", "1"}, m = 1, n = 1
    输出: 2

    解释: 你可以拼出 "10",但之后就没有剩余数字了。更好的选择是拼出 "0" "1"

    分析:题目给定一个仅包含0和1字符串的数组。任务是从数组中选取尽可能多的字符串,使这些字符串包含的0和1的数目分别不超过m和n。

    我们把每个字符串看做是一件物品,把字符串中0的数目和1的数目看做是两种“重量”,所以就变成了一个二维01背包问题,书包的两个限重分别是m和n,要求书包能装下的物品的最大数目(也相当于价值最大,设每个物品的价值为1).

    我们提前把每个字符串的两个“重量”w0,w1算出来用数组存放,但是注意到只需要用一次这两个值,所以我们只需在用到的时候计算w0和w1就行了,这样就不用例外的数组存放。

    class Solution {
       public int findMaxForm(String[] strs, int m, int n) {
           if(strs==null) return 0;
           int num=strs.length;
           int w0,w1;
           int[][] dp=new int[m+1][n+1];
           for(int i=1;i<=num;i++){
               w0=0;
               w1=0;
               for(char ch:strs[i-1].toCharArray()){
                   if(ch=='0') w0++;
                   else w1++;
              }

               for(int j=m;j>=w0;j--){
                   for(int k=n;k>=w1;k--){
                       dp[j][k]=Math.max(dp[j][k],1+dp[j-w0][k-w1]);
                  }
              }
          }
           return dp[m][n];
      }
    }

     

    恰好装满0/1背包问题计算方案数

    dp[i][j]=sum(dp[i-1][j],dp[i][j-w[i]])

    494. 目标和

    难度中等338

    给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 +-。对于数组中的任意一个整数,你都可以从 +-中选择一个符号添加在前面。

    返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

    示例:

    输入:nums: [1, 1, 1, 1, 1], S: 3
    输出:5
    解释:

    -1+1+1+1+1 = 3
    +1-1+1+1+1 = 3
    +1+1-1+1+1 = 3
    +1+1+1-1+1 = 3
    +1+1+1+1-1 = 3

    一共有5种方法让最终目标和为3。

    提示:

    • 数组非空,且长度不会超过 20 。

    • 初始的数组的和不会超过 1000 。

    • 保证返回的最终结果能被 32 位整数存下。

    class Solution {
       public int findTargetSumWays(int[] nums, int S) {
           int sum=0;
           for(int x:nums){
               sum+=x;
          }
           if(S>sum||S<-sum) return 0;
           if(((S+sum)&1)==1) return 0;
           int target=(S+sum)>>1;
           int[] dp=new int[target+1];
           dp[0]=1;
           for(int i=1;i<=nums.length;i++){
               for(int j=target;j>=nums[i-1];j--){
                   dp[j]=dp[j]+dp[j-nums[i-1]];
              }
          }
           return dp[target];
      }
    }

    恰好装满完全背包问题求最小价值

    322. 零钱兑换

    难度中等724

    给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

    示例 1:

    输入: coins = [1, 2, 5], amount = 11
    输出: 3
    解释: 11 = 5 + 5 + 1

    示例 2:

    输入: coins = [2], amount = 3
    输出: -1

    说明: 你可以认为每种硬币的数量是无限的。

    //完全背包问题思路伪代码(空间优化)
    dp[0,...,W]=0;
    for i=1,...,N
       for j=w[i],...,W//必须正向枚举!!!
           dp[j]=max(dp[j],dp[j-w[i]]+v[i])

    分析:如果我们把面值看做物品,面值金额看成是物品的重量,每件物品的价值均为1,这样此题就是一个恰好装满的完全背包问题了。不过这里不是求最多装入多少物品而是求最少,可以将每个物品的价值看成1,求最少价值。

    class Solution {
       public int coinChange(int[] coins, int amount) {
           int len=coins.length;
           int[] dp=new int[amount+1];
           Arrays.fill(dp,Integer.MAX_VALUE);
           dp[0]=0;
           
           for(int i=0;i<len;i++){
               for(int j=coins[i];j<=amount;j++){
                   if(dp[j-coins[i]]!=Integer.MAX_VALUE){
                       dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
                  }
              }
          }
           return dp[amount]==Integer.MAX_VALUE?-1:dp[amount];
      }
    }

    恰好装满0/1完全背包问题求方案数

    518. 零钱兑换 II

    难度中等207

    给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

    示例 1:

    输入: amount = 5, coins = [1, 2, 5]
    输出: 4
    解释: 有四种方式可以凑成总金额:
    5=5
    5=2+2+1
    5=2+1+1+1
    5=1+1+1+1+1

    示例 2:

    输入: amount = 3, coins = [2]
    输出: 0
    解释: 只用面额2的硬币不能凑成总金额3。

    示例 3:

    输入: amount = 10, coins = [10] 
    输出: 1

     

    注意**:**

    你可以假设:

    • 0 <= amount (总金额) <= 5000

    • 1 <= coin (硬币面额) <= 5000

    • 硬币种类不超过 500 种

    • 结果符合 32 位符号整数

    class Solution {
       public int change(int amount, int[] coins) {
           int len=coins.length;
           int[] dp=new int[amount+1];
           
           dp[0]=1;
           
           for(int i=0;i<len;i++){
               for(int j=coins[i];j<=amount;j++){
                   dp[j]=dp[j]+dp[j-coins[i]];
              }
          }
           return dp[amount];
      }
    }

    恰好01背包问题

    416. 分割等和子集

    难度中等341

    给定一个只包含正整数非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

    注意:

    1. 每个数组中的元素不会超过 100

    2. 数组的大小不会超过 200

    示例 1:

    输入: [1, 5, 11, 5]

    输出: true

    解释: 数组可以分割成 [1, 5, 5] 和 [11].

    示例 2:

    输入: [1, 2, 3, 5]

    输出: false

    解释: 数组不能分割成两个元素和相等的子集.

    https://mp.weixin.qq.com/s?src=11&timestamp=1596211005&ver=2494&signature=cLm--axb39CcQxxgdQSwLgGzsZE5pHVZe0QXYdkTIk7XWDLEJgosT-RKJbp19fgD9XvfAokmbuBfjQR6UKWvnGrc77lxwWTKgfJA4JQf3xVHoAHsr-gPh7wD*rDlpOdf&new=1

    class Solution {
       public boolean canPartition(int[] nums) {
           int sum=0;
           for(int i=0;i<nums.length;i++){
               sum+=nums[i];
          }
           if((sum&1)==1) return false;
           sum=sum>>1;
           boolean[] dp=new boolean[sum+1];
           Arrays.fill(dp,false);//装不满
           dp[0]=true; //装满了
           for(int i=1;i<=nums.length;i++){
               for(int j=sum;j>=nums[i-1];j--){
                   dp[j]=dp[j]||dp[j-nums[i-1]];
              }
          }
           return dp[sum];
      }
    }

    面试题 08.11. 硬币

    难度中等155

    硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)

    示例1:

     输入: n = 5
    输出:2
    解释: 有两种方式可以凑成总金额:
    5=5
    5=1+1+1+1+1

    示例2:

     输入: n = 10
    输出:4
    解释: 有四种方式可以凑成总金额:
    10=10
    10=5+5
    10=5+1+1+1+1+1
    10=1+1+1+1+1+1+1+1+1+1

    说明:

    注意:

    你可以假设:

    • 0 <= n (总金额) <= 1000000

    class Solution {
       public int waysToChange(int n) {
           int[] nums=new int[]{1,5,10,25};
           int[] dp=new int[n+1];
           dp[0]=1;
           for(int i=0;i<4;i++){
               for(int j=nums[i];j<=n;j++){
                   dp[j]=(dp[j]+dp[j-nums[i]])% 1000000007;
              }
          }
           return dp[n];
      }
    }

     

  • 相关阅读:
    openstack controller ha测试环境搭建记录(七)——配置glance
    openstack controller ha测试环境搭建记录(六)——配置keystone
    openstack controller ha测试环境搭建记录(五)——配置rabbitmq集群
    spring classpath & classpath*
    SVN相关
    eclipse安装springboot插件
    Hbase Shell常用命令
    CentOS下安装Hbase
    CentOS安装JDK-tar.gz文件
    Linux常用命令
  • 原文地址:https://www.cnblogs.com/Susie2world/p/13413906.html
Copyright © 2011-2022 走看看