max日期最大值为0_0-1背包问题和vivo运矿石问题

329b41d043fbf9ffe8d0d8d77420bffb.png

0-1背包问题和vivo运矿石问题

最近刷vivo运矿石笔试题,题目如下:

链接:https://www.nowcoder.com/questionTerminal/b58f922cc924478fa1e2dca2cc4f4eb7
来源:牛客网

小v最近在玩一款挖矿的游戏,该游戏介绍如下:
1、每次可以挖到多个矿石,每个矿石的重量都不一样,挖矿结束后需要通过一款平衡矿车运送下山;
2、平衡矿车有左右2个车厢,中间只有1个车轮沿着导轨滑到山下,且矿车只有在2个车厢重量完全相等且矿石数量相差不超过1个的情况下才能成功运送矿石,否则在转弯时可能出现侧翻。
假设小v挖到了n(n<100)个矿石,每个矿石重量不超过100,为了确保一次性将n个矿石都运送出去,一旦矿车的车厢重量不一样就需要购买配重砝码。请问小v每次最少需要购买多少重量的砝码呢? (假设车厢足够放下这些矿石和砝码,砝码重量任选)

题目乍一看有点类似0-1背包问题,而0-1背包问题是一道典型的可以利用动态规划解决的问题。下面先来回顾一下利用动态规划解决0-1背包问题。

不想看的童鞋可以直接跳到最后,有通过牛客网所有测试用例的Java代码,我还把牛客网评论里贴的代码下的评论(那些代码有哪些测试用例没过的)进行了测试,也都过了。当然不排除我的代码也有某些条件没考虑到会有一些测试用例通不过,欢迎指正。


0-1背包问题

问题描述:有一个背包最多能放W千克的东西,现在有N个物品,每个物品都有对应的质量wt[i]和价值val[i](i = 1,2,...,N), 问该背包最多能装多少价值的东西,注意物品不能切割。

这是一道经典的动态规划问题。关于动态规划的问题,这一系列的文章讲得挺好的,可以参考一下:

动态规划解题套路框架​labuladong.gitbook.io
25d7938df4f2c1d540fd0adae23496a5.png

影响该问题的状态有两个:背包容量 和 可选的物品

选择是: 装 和 不装 进背包

因为有两个状态,所以我们需要一个二维的dp数组

dp[i][w]表示对于前i个物品,当前的背包承重为w,在这种情况下能装的最大价值为dp[i][w]

最终我们需要的答案就是dp[N][W]

初始状态:dp[0][w]=0 , dp[i][0]=0 , 很容易理解,当没有物品或者包没有承重时,最多能装的价值为0。

可以整理出如下的伪代码:

int dp[N+1][W+1];
dp[0][] = 0;
dp[][0] = 0;

for i in [1..N]{
    for w in [1..W]{
        dp[i][w] = max(选择装i,选择不装i);
    }
}

return dp[N][W];

接下来我们要考虑的就是max(选择装i,选择不装i)的具体取值了。

再强调一下dp[i][w]表示的是对于前i个物品,当前的背包承重为w,在这种情况下能装的最大价值

选择不装i:即,不装i价值最大,此时dp[i][w]=dp[i-1][w]

选择装i:即,此时最大的价值为物体i的价值加上 (前i-1个物体)在(背包容量为【当前容量w-物品i的重量】)的情况下的最大价值。换个角度说就是你装了i,就得考虑,剩余承重限制下的最大值。表示为dp[i][w]=dp[i-1][w-wt[i-1]] + val[i-1], 注意一维数组的下标i-1表示第i个物体的属性。

当然选择装物品i是有条件的,就是物品i的重量wt[i-1]不能大于当前承重w

完整代码如下:

public static int knapsack(int W,int N,int[] weight,int[] value){
        //初始化
        int[][] dp = new int[N+1][W+1];
        for (int i=0;i<=N;++i){
            dp[i][0] = 0;
        }
        for (int w=0;w<=W;++w){
            dp[0][w] = 0;
        }

        for (int i=1;i<=N;++i){
            for (int w=1;w<=W;++w){
                //只能选择不装
                if (w - weight[i-1] < 0){
                    dp[i][w] = dp[i-1][w];
                } else {
                    dp[i][w] = Math.max(dp[i-1][w-weight[i-1]]+value[i-1],dp[i-1][w]);
                }
            }
        }
        return dp[N][W];
    }

vivo运矿石问题

利用上面连接给出的解决动态规划问题的套路,首先明确状态,哪些值会对结果产生影响。与0-1背包问题类似,[可选择的矿石]和[容量]是和明显的两个状态,但是本题对矿石的数量也有限制,因此[拿取矿石数量]是第三个状态。

dp数组,是一个三维的布尔型数组。dp[i][j][k]表示从前i个矿石中拿j个,质量是否等于k。

因此可以整理出如下伪代码:

boolean dp[I+1][J+1][K+1]
初始化dp
for i in [0..I]{
    for j in [0..J]{
        for k in [0..K]{
            状态转移
        }
    }
}

接下来要考虑的就是初始化和状态转移了。

初始化很简单,因为我们对i的遍历是从0开始的,所以我们要找出i=0时所有为true的情况,显然只有dp[0][0]为true。

状态转移,我们只要考虑对下一层(dp[i+1][][])的影响,不要跳(比如当dp[i][j][k]为true时,dp[i+n][j][k]显然也为true,n为索引范围内的正整数),不然容易乱。可以发现,dp[i][j][k]为true时对下一层的影响有dp[i+1][j][k]为true,dp[i+1][j+1][k+weight[i]]表示把第i+1个也拿进去,weight[i]是因为索引是从0开始的。

上面的伪代码可以整理成如下:

boolean dp[I+1][J+1][K+1]
dp[0][0][0]=true;
for i in [0..I]{
    for j in [0..J]{
        for k in [0..K]{
            if(dp[i][j][k]){
                dp[i+1][j][k] = true;
                dp[i+1][j+1][k+weight[i]] = true;
            }
        }
    }
}

你以为到这儿就结束了?确实,动态规划的部分到这儿基本就完成了,但这道题目麻烦就在各种小的细节。

先说I, J, K的问题,

I表示输入数组的长度,没什么好说的。

J 只要数组长度的一半就可以了,因为左右两个矿车的矿石数量差值不能超过1,但是记得得向上取整。

K,取值输入数组的总和sum.

在状态转移的时候注意k+weight[i]不能超过索引范围。

最后得到了dp数组,我们求的就是dp[I][J][k]dp[I][J-1][k](I为奇数)为true的情况下的sum-k-k绝对值(砝码重量)的最小值。

完整的Java代码如下:

private static int solution(int[] input) {

        int I = input.length;
        int J = (int)Math.ceil(input.length/2.0);
        int sum = 0;
        for (int i=0;i<I;++i){
            sum += input[i];
        }
        int K = sum;
        boolean[][][] dp = new boolean[I+1][J+1][K+1];

        //初始化dp数组
        dp[0][0][0] = true;
        //选择
        for (int i=0;i<I;++i){
            for (int j=0;j<=J && j<=i;j++){
                for (int k=0;k<=K;++k){
                    if (dp[i][j][k]){
                        dp[i+1][j][k] = true;
                        if (k+input[i] <= K && j<J){
                            dp[i+1][j+1][k+input[i]] = true;
                        }
                    }
                }
            }
        }
        int ans = sum;
        int cur = sum;
        for (int k=K;k>=0;k--){
            if (dp[I][J][k]){
                cur = Math.abs(sum-k-k);
                ans = ans<cur ? ans : cur;
            }
        }
        if (I%2 != 0){
            for (int k=K;k>=0;k--){
                if (dp[I][J-1][k]){
                    cur = Math.abs(sum-k-k);
                    ans = ans<cur ? ans : cur;
                }
            }
        }
        return ans;
    }

93dc6050699911964839b2ab88f94889.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值