动态规划-01背包系列题

动态规划-01背包系列题,选取动态规划中采用01背包模型解题思路的几个例题

1.01背包问题解题思路
2.01背包问题实现
3.01背包系列-最多任务工资
4.01背包系列-数列不相邻数字最大和
5.01背包系列-数列找到指定和子数列


1.01背包问题解题思路
从右到左进行解析,分析第n个物品的状态,第n个物品只有“放”和“不放“俩种状态,在“放”状态下,某些变量发生变化,以及放情况下,与前面子问题之间的联系。在“不放”状态下,即考虑前n-1个物品。
每个物品都有俩种状态,动态规划,相当于列出所有情况,从中挑选子问题的最优解。
所以首先要从后往前分析问题,给出状态转移方程,然后判定边界情况,然后从前往后实现代码,填充中间数组(一维数组,二维数组)。

2.01背包问题实现
题目:有一个包和n个物品,包的容量为m,每个物品都有各自的体积和价值。问当这n个物品放在包里而物品的体积总数不超过包的容量m时,如何让背包具有最大价值?

分析:
1.建模:使用数组分别存储这n个物品的体积和价值
v[n] = {v1,v2,v3,….vn};
w[n] = {w1,w2,w3,..wn};

2.确定子问题:n个物品加入容量为m的包里f(n,m),第n个物品有俩种选择,放或不放,如果放,包的剩余容量为m-vn,价值为wn + f(n-1,m-vn)
如果不放,包的剩余容量为m,价值为f(n-1,m)

3.状态转移方程:f(n,m) = max{wn+f(n-1,m-vn),f(n-1,m)}

4.边界情况:

  • n == 0 retutn 0
  • m ==0 return 0
  • m < 0 return f(n-1,m)

5.动态规划二维数组(画图更直观)

dp[i][j] = max{dp[i-1][j-vi]+wi,dp[i-1][j]}
dp[i][j]表示前i件物品中,选择若干件放在容量为j的包里的最大价值1
这里写图片描述
6..代码实现
代码中给出递归法和动态规划法,用以比较

public class Bags {
    public static void main(String[] args){
        int[] weight = {3,4,7,9,13};
        int[] volume = {2,4,3,5,7};
        int m = 11;
        int n = weight.length-1;
        int res = getMaxValue(volume,weight,m,n);
        System.out.println(res);
        int res_dp = getMaxValue_DP(volume,weight,m);
        System.out.println(res_dp);
    }

    /**
     * 使用二维数组保存中间结果
     * @param
     * @param m
     * @return
     */
    private static int getMaxValue_DP(int[] volume, int[] weight, int m) {
        if (volume.length == 0)
            return 0;
        if (m == 0)
            return 0;
        int[][] dp = new int[volume.length][m];
        //填充第一列
        for (int i = 0; i < dp.length; i++){
            if (volume[i] <= 1){
                dp[i][0] = dp[i][0] < weight[i] ? weight[i] : dp[i][0];
            }
        }
        //填充第一行
        for (int i = 0; i < m; i++){
            if (volume[0] <= i+1){
                dp[0][i] = weight[0];
            }
        }

        //从左到右,从上到下依次填充
        for (int i = 1; i < weight.length; i++){
            for (int j = 1; j < m; j++){
                int a = dp[i-1][j];
                int b = Integer.MIN_VALUE;
                if (j-volume[i] >= 0)
                    b = dp[i-1][j-volume[i]]+weight[i];
                dp[i][j] = Math.max(a,b);
            }
        }
        return dp[volume.length-1][m-1];
    }

    /**
     * 递归方法-获得最大质量
     * 存在重叠子问题,空间复杂度,时间复杂度高
     * @param
     * @param m
     * @return
     */
    private static int getMaxValue(int[] volume, int[] weight, int m,int n) {
        if (m == 0 || n == 0)
            return 0;
        if (volume[n] > m)
            return getMaxValue(volume, weight, m, n - 1);
        int a = getMaxValue(volume, weight, m, n - 1);//不放
        int b = getMaxValue(volume, weight, m - volume[n], n - 1) + weight[n];//放
        return Math.max(a,b);
    }
}

说明:此题中动态规划先填充第一行,第一列,然后依次从左到右,从上到下,填充二维数组中其他部位。

3.最多任务工资

问题:每个任务都有开始时间和结束时间,完成每个任务都有相应的工资,但执行每个任务不能有时间冲突,求所能赚取的最大工资数。
这里写图片描述
分析:同样和上面解题思路一致

1.建模:

使用数组分别存储n个任务的开始时间,结束时间,以及工资
start[n] = {s1,s2,…sn};
end[n] = {e1,e2,…en};
money[n] = {m1,m2,..mn};

2.确定子问题:

执行n个任务f(n),第n个任务有俩种状态,做或不做。
如果做,f(n) = money(n) + f(pre[n])
如果不做,f(n) = f(n-1)
pre[n]表示是在不冲突情况下,第n个任务的前一个可执行任务

3.状态转移方程:

f(n) = max{money(n) + f(pre[n]),f(n-1)}

4.边界情况:

n == 0 return 0
pre[n] < 0 return f(n-1)

5.动态规划-数组(一维数组)

6.代码实现:

package com.zd.dp;


import java.util.Arrays;

/**
 * 工作任务问题
 * 求赚取的最大金额数
 */
public class Task {
    public static void main(String[] args){
        int n = 5;
        int[] start = {1,2,3,5,6};
        int[] end = {3,5,6,9,10};
        int[] money = {5,10,4,12,10};
        int res = getMaxMoney(n,start,end,money);
        System.out.println(res);
        int res_dp = getMaxMoney_dp(n,start,end,money);
        System.out.println(res_dp);
    }

    /**
     * 使用动态规划解决
     * @param n
     * @param start
     * @param end
     * @param money
     * @return
     */
    private static int getMaxMoney_dp(int n, int[] start, int[] end, int[] money) {
        if (n == 1)
            return money[0];
        int[] pre = new int[n];//上一个可以执行的最近任务
        for (int i = n-1; i >= 0; i--){
            pre[i] = -1;
            for (int j = i-1; j >= 0; j--){
                if (end[j] <= start[i]) {
                    pre[i] = j;
                    break;
                }
            }
        }
        int[] dp = new int[n];
        for (int i = 1; i < n; i++){
            int a = money[i];
            int b = dp[i-1];
            if (pre[i] != -1)
                a += dp[pre[i]];
            dp[i] = Math.max(a,b);
        }
        return dp[n-1];
    }

    private static int getMaxMoney(int n, int[] start, int[] end, int[] money) {
        if (n == 0)
            return 0;
        int[] pre = new int[n];//每个任务上一个可以执行的任务
        for (int i = n-1; i >= 0; i--){
            pre[i] = -1;
            for (int j = i-1; j >= 0; j--){
                if (end[j] <= start[i]) {
                    pre[i] = j;
                    break;
                }
            }
        }
        System.out.println(Arrays.toString(pre));
        int res = getMaxMoneys(n-1,money,pre);
        return res;
    }

    private static int getMaxMoneys(int i, int[] money, int[] pre) {
        if (i < 0)
            return 0;
        int a = getMaxMoneys(i-1,money,pre);//不做
        int b = getMaxMoneys(pre[i],money,pre)+money[i];//做
        return Math.max(a,b);
    }
}

4.不相邻数字最大和

问题:在数列中选择数字,需要保证不能选择相邻数字,要求所选的数字和最大

分析:在前n个数字中,第n个数字有俩种选择,选或不选,如果选,下一个可选位置从n-2处开始。

状态转移方程:f(n) = max{f(n-1),a[n-1]+f(n-2)}

边界情况:
n==1 return a[0]
n==2 return Math.max(a[0],a[1])

代码实现:

package com.zd.dp;

/**
 * 从数组中选出不相邻的数字,要求所选的数字和最大
 */
public class SelectMaxSum {
    public static void main(String[] args){
        int[] array = {5,6,1,2,7};
        int n = 5;
        int res = getMax(array,n-1);
        System.out.println(res);
        int res_dp = getMax_dp(array,n);
        System.out.println(res_dp);
    }

    private static int getMax_dp(int[] array, int n) {
        int[] dp = new int[n];
        dp[0] = array[0];
        dp[1] = Math.max(array[0],array[1]);
        for (int i = 2; i < n; i++)
            dp[i] = Math.max(array[i] + dp[i-2],dp[i-1]);
        return dp[n-1];
    }

    private static int getMax(int[] array, int n) {
        if (n == 0)
            return array[0];
        if (n == 1)
            return Math.max(array[0],array[1]);
        int a = array[n] + getMax(array,n-2);
        int b = getMax(array,n-1);
        return Math.max(a,b);
    }
}

5.判断指定和的子序列是否存在

问题:给定值,判断在数列中是否存在和为给定值的子数列

分析:

状态转移方程:
f(n,m) = f(n-1,m) || f(n-1,m-a[n-1])

边界情况:
m == 0 return true
m < 0 return false
n < 0 return false

代码实现:

package com.zd.dp;

/**
 * 给定值,判断数列中是否存在和为给定值的子数列
 */
public class IsSumOfValue {
    public static void main(String[] args){
        int[] a = {5,6,1,2,7,4};
        int sum = 9;
        int i = a.length;
        boolean res = isSumOfValue(a,sum,i-1);
        System.out.println(res);

        boolean res_dp = isSumOfValue_dp(a,sum,i);
        System.out.println(res_dp);
    }

    /**
     * 动态规划解决
     * @param a
     * @param sum
     * @param i
     * @return
     */
    private static boolean isSumOfValue_dp(int[] a, int sum, int n) {
        boolean[][] dp = new boolean[n][sum];
        //填充第一行
        for (int i = 0; i < sum; i++){
            dp[0][i] = a[0] == i ? true:false;
        }
        //填充第一列
        for (int i = 1; i < n; i++){
            dp[i][0] = true;
        }
        //从左到右,从上到下依次填充二维数组
        for (int i = 1; i < n; i++){
            for (int j = 1; j < sum; j++){
                boolean A = false;//选
                boolean B = dp[i-1][j];//不选
                if (sum - a[i] > 0)
                    A = dp[i-1][sum-a[i]];
                dp[i][j] = A || B;
            }
        }
        boolean res = false;
        for (int i = 0; i < n; i++){
            res = res || dp[i][sum-1];
        }
        return res;
    }

    /**
     * 递归解决
     * @param a
     * @param sum
     * @param i
     * @return
     */
    private static boolean isSumOfValue(int[] a, int sum, int i) {
        if (sum == 0)
            return true;
        if (sum < 0)
            return false;
        if (i < 0)
            return false;
        boolean res = isSumOfValue(a,sum-a[i],i-1) || isSumOfValue(a,sum,i-1);
        return res;
    }
}

总结:分析过程即是从后往前分析,解题是从前往后解题,动态规划过程,即是填充数组元素过程。重点是确定状态转移方程,有时候边界情况也不好确定。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值