动态规划入门相关例题总结

题目来源:198. 打家劫舍 - 力扣(LeetCode)


打家劫舍是一道经典的dp入门题,具体思路可以参考笔者上一篇。我们首先明确这道题的原问题和子问题,显然,原问题就是对于 n 个房屋,我们偷窃能够获得最大收益是多少;子问题就是对于前 i 间房屋,我们能获得的最大收益是多少。那么,这个问题的状态(自变量)就是房屋的数量。

确定了问题的dp数组含义以及状态,我们就可以来分析如何构建状态转移方程了

首先,我们对于dp问题要明确一点,思考方式往往是自底向上思考的,所以我们就先从状态转移方程的边界情况进行考虑,因为边界情况往往是问题的最简单的情况。假设只有一间房屋,我们就没有选择,只能偷这间房屋;假设有两间房屋,根据题目要求,不能偷两间相邻的房屋,我们就得在两间房屋中偷金额最大的一间。

因此我们可以给出状态转移方程的第一部分

if (i == 0) {
        dp[i] = nums[i];
} else if (i == 1) {
        dp[i] = max(nums[i - 1], nums[i]);
}

假设有超过两间房屋,我们就从最后一间房屋开始考虑,如果说我们可以偷最后一间房屋,那么倒数第二间房屋肯定是不能被偷窃的,因此我们能获得的最大收益就是偷取前 n - 2 间房屋能获得的最大收益 + 偷窃最后一间房屋获得的收益;如果说我们不能偷最后一间房屋,那么倒数第二间房屋肯定是被偷窃了,因此我们能获得的最大收益就是我们偷窃前 n - 1 间房屋能获得的最大收益

由此,我们可以确定状态转移方程的第二部分

dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);

我们将dp数组的最后一个元素输出即可。参考代码如下

class Solution {
public:
    int rob(vector<int>& nums) {
        
        int dp[105] = {0}; //dp数组表示偷前i间房子能获得的最大价值

        for (int i = 0; i < nums.size(); i++) { //遍历填充dp数组
            if (i == 0) {
                dp[i] = nums[i];
            } else if (i == 1) {
                dp[i] = max(nums[i - 1], nums[i]);
            } else {
                dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
            }
        }
        return dp[nums.size() - 1];
    }
};


题目来源:LCR 166. 珠宝的最高价值 - 力扣(LeetCode)


打家劫舍问题可以通过一维dp数组来解决,那么珠宝的最高价值问题就可以通过二维dp数组解决。

同样的,我们首先分析dp数组的含义。根据题意,每个珠宝都对应着一个坐标(i, j),我们要求的是从架子左上角一直拿到架子右下角能拿到的最高价值,所以我们定义dp数组的含义为:dp[i][j]表示从左上角开始拿,一直拿到坐标为(i, j)的位置能拿到的最大价值。


确定了dp数组的含义,我们就可以来思考如何确定状态转移方程了。首先确定方程的边界情况,对于整个架子,可以看成一个二维数组。一般来说,除了位于边界上的点,我们要到达该二维数组上的某一点,只能够从该点的左边或者该点的上方到达,那么我们的边界情况就可以分为两种情况:在上边界与在左边界。在上边界的点只能够由该点的左边到达,在左边界上的点只能够由该点的上方到达,因此我们就确定了该dp数组的边界条件。

if (i == 0 && j == 0) {
            dp[i][j] = frame[i][j];
} else if (i == 0) {
            dp[i][j] = dp[i][j - 1] + frame[i][j];
} else if (j == 0) {
            dp[i][j] = dp[i - 1][j] + frame[i][j];
}

对于一般的点,既可以从该点的左边到达也可以从该点的上方到达,至于如何选择,取决于从左边到达能拿到的价值多还是从上方到达能拿到的价值多,因此可以得到方程的一般情况。

dp[i][j] = max(dp[i - 1][j] + frame[i][j], dp[i][j - 1] + frame[i][j]);

最后输出dp数组右下角的元素即可。参考代码如下

class Solution {
public:
    int jewelleryValue(vector<vector<int>>& frame) {
        int dp[205][205]; //dp数组含义为坐标为(i, j)的货架能拿到的最大价值
        for (int i = 0; i < frame.size(); i++) {
            for (int j = 0; j < frame[i].size(); j++) {
                if (i == 0 && j == 0) {
                    dp[i][j] = frame[i][j];
                } else if (i == 0) {
                    dp[i][j] = dp[i][j - 1] + frame[i][j];
                } else if (j == 0) {
                    dp[i][j] = dp[i - 1][j] + frame[i][j];
                } else {
                    dp[i][j] = max(dp[i - 1][j] + frame[i][j], dp[i][j - 1] + frame[i][j]);
                }
            }
        }
        return dp[frame.size() - 1][frame[frame.size() - 1].size() - 1];
    }
};


题目来源:洛谷P1048


这道题是典型的01背包问题,给定背包容量T与金币堆数M,求背包能装的最大价值。首先明确解决01背包问题用贪心算法是不行的,需要采用动态规划算法。

首先,用weight[i]表示物品重量,value[i]表示物品价值,然后我们来定义dp数组的含义:dp[i][j]表示以容量为 j 的背包放入前 i 个物品的最大价值。

明确了dp数组的含义,接下来来确定状态转移方程。

与笔者上一篇文章的例题类似,我们需要考虑当前背包容量与当前遍历到的物品重量的大小关系,倘若背包容量比当前物品的重量小,那么我们就只能在该物品的前方所有物品中选取最大价值的物品,即dp[i][j] = dp[i - 1][j]倘若背包容量充足,对于每个物品就有装这个物品与不装这个物品两种情况,我们要在这两种情况中找到最大价值的情况,因此就有:dp[i][j] = max(dp[i - 1][j - weight[i]] + value[i], dp[i - 1][j]);其中,dp[i - 1][j - weight[i]] + value[i]代表装入该物品,dp[i - 1][j]代表不装入该物品,对两种情况取最大值即可,最终输出结果。参考代码如下:

#include <iostream>
#include <math.h>
using namespace std;
int T, M;
int weight[105], value[105]; //weight表示草药重量,value表示草药价值
int dp[105][1005]; //dp[i][j]表示用j个容量取前i个草药的最大价值

int main() {
    cin >> T >> M; //T表示采药的总时间,M表示草药的数目
    for (int i = 1; i <= M; i++) {
        cin >> weight[i] >> value[i];
    }

    for (int i = 1; i <= M; i++) {
        for (int j = T; j >= 0; j--) {
            if (j >= weight[i]) {
                dp[i][j] = max(dp[i - 1][j - weight[i]] + value[i], dp[i - 1][j]);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    cout << dp[M][T] << endl;
    return 0;
}

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YUKIPEDIA~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值