DP刷题练习(三)

简介: 本文围绕动态规划(DP)刷题展开,内容基于代码随想录的学习总结。文章通过多个经典LeetCode题目讲解了动态规划的应用,包括打家劫舍系列(198、213、337)、买卖股票系列(121、122、123、188、309)等。从线性DP到树形DP,逐步深入解析状态定义、转移方程和初始化方法。例如,打家劫舍问题中通过`dp[i]`表示前i个房间的最大收益;股票买卖问题则用二维数组`dp[i][j]`记录第i天不同操作后的最大利润。每道题都附有详细代码与逻辑推导,帮助理解动态规划的核心思想及其实现技巧。

DP刷题练习(三)

文章内容学习自代码随想录,感谢carl!!!!

[TOC]

198. 打家劫舍 - 力扣(LeetCode)

int rob(vector<int>& nums) {
   
        int n=nums.size();
        if(n==0) return 0;
        if(n==1) return nums[0];
        int dp[n+1];//考虑下标i(包含),前i个房间所能偷盗最大金币为dp[i]
        dp[0]=nums[0];dp[1]=max(nums[0],nums[1]);
        for(int i=2;i<n;i++)
        {
   
            dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
        }       
        return dp[n-1];
    }
//对于当前的房间都有不偷和偷两种状态
//不偷是dp[i]=dp[i-1]
//偷是dp[i]=dp[i-2]+nums[i]

213. 打家劫舍 II - 力扣(LeetCode)

打家劫舍二与一的区别在于,头尾是连成环的,就是说头尾是相邻的,针对头尾,我们有三种情况

①头尾都不偷

②头偷尾不偷

③头不偷尾偷

其实又可以发现我们可以将①包含于②③中,我们考虑0---n-2与1---n-1,并没有说我们一定要偷0,或一定要偷n-1,只是不考虑两者同时出现的情况所以我们只要用俩个dp数组分别考虑0---n-2与1---n-1,取其最大值便是结果,其余都与上题一致

int rob(vector<int>& nums) {
   
        int n=nums.size();
        if(n==0) return 0;
        else if(n==1) return nums[0];
        else if(n==2) return max(nums[0],nums[1]);
        int dp1[n-1],dp2[n-1];//定义两个长度为n-1的数组即可
        //对于dp1,其考虑,不包涵最后一个元素的情况
        dp1[0]=nums[0],dp1[1]=max(nums[0],nums[1]);
        for(int i=2;i<n-1;i++) dp1[i]=max(dp1[i-1],dp1[i-2]+nums[i]);

        //构建一个nums2数组服务于dp2,dp2考虑不包含第一个元素的情况
        int nums2[n-1];
        for(int i=0;i<n-1;i++) nums2[i]=nums[i+1];
        dp2[0]=nums2[0],dp2[1]=max(nums2[0],nums2[1]);
        for(int i=2;i<n-1;i++) dp2[i]=max(dp2[i-1],dp2[i-2]+nums2[i]);

        return max(dp1[n-2],dp2[n-2]);
    }

337. 打家劫舍 III - 力扣(LeetCode)

树形dp入门

我们设置状态dp【0】为不偷当前节点所能获得的最大值,dp【1】为偷当前节点所能获得的最大值

我们采用后序遍历,从下往上遍历,最后看偷根结点,不偷根结点,所能获得的最大值是什么

//偷当前节点:int val1=cur->val+leftdp[0]+rightdp[0];
//当前节点若偷,则其左右儿子不能偷
//不偷当前节点:int val2=max(leftdp[0],leftdp[1])+max(rightdp[0],rightdp[1]);
//当前节点若不偷,则其左右各取所获最大价值
vector<int> robtree(TreeNode *cur)
    {
   
        if(cur==NULL) return vector<int> {
   0,0};
        vector<int> leftdp=robtree(cur->left);
        vector<int> rightdp=robtree(cur->right);
        //偷当前节点
        int val1=cur->val+leftdp[0]+rightdp[0];
        //不偷当前节点
        int val2=max(leftdp[0],leftdp[1])+max(rightdp[0],rightdp[1]);
        return vector<int> {
   val2,val1};
    }
    int rob(TreeNode* root) {
   
        vector<int> result=robtree(root);
        return max(result[0],result[1]);
    }

121. 买卖股票的最佳时机 - 力扣(LeetCode)

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。

就是你的买卖都只能有一次

dp含义如代码所示

dp[i][0]=max(dp[i-1][0],-prices[i]);
//第i天持有股,你可能是之前一天就持有,或者是今天买入才持有,说到底是找最小的价格买入
 dp[i][1]=max(dp[i-1][1],prices[i]+dp[i-1][0]);
//第i天不持有股,你可以是前一天就不持有了,也可以是前一天就持有了,但是今天卖出

初始化:

dp[0][0]=-prices[0]; //第0天持股,那你第0天肯定要买入!
dp[0][1]=0;          //第0天不持股
int maxProfit(vector<int>& prices) {
   
        int n=prices.size();
        int dp[n][2];//dp[i][0]表示第i天持有股票的所获得的最大利润
                     //dp[i][1]表示第i天不持有股票所获得的最大利润
        dp[0][0]=-prices[0];
        dp[0][1]=0;
        for(int i=1;i<n;i++){
   
            dp[i][0]=max(dp[i-1][0],-prices[i]);
            dp[i][1]=max(dp[i-1][1],prices[i]+dp[i-1][0]);
        }
        return dp[n-1][1];
    }

122. 买卖股票的最佳时机 II - 力扣(LeetCode)

本题与上题的区别是股票可以买卖多次,所以你买入股票时你当前拥有的金额可以不是0,所以在此处持股的最大利润如下

dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);

本题有个坑点就是题目说股票可以同一天买入然后卖出

但是题目说了你最多只能持有一支股票,所以你今天要买入,必然前一天不持股所以是dp【i-1]】【1】-prices【i】

然后今天又卖出所以是dp【i-1]】【1】-prices【i】+prices【i】<==> dp【i-1】【1】

   int maxProfit(vector<int>& prices) {
   
        int n=prices.size();
        int dp[n][2];//dp[i][0]表示第i天持有股票的所获得的最大利润
                     //dp[i][1]表示第i天不持有股票所获得的最大利润
        dp[0][0]=-prices[0];
        dp[0][1]=0;
        for(int i=1;i<n;i++){
   
            dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }
        return dp[n-1][1];
    }

123. 买卖股票的最佳时机 III - 力扣(LeetCode)

int maxProfit(vector<int>& prices) {
   
        int n=prices.size();
        int dp[n][5];//dp[i][0]表示不操作
                     //dp[i][1]表示第一次持有
                     //dp[i][2]表示第一次不持有
                     //dp[i][3]表示第二次持有
                     //dp[i][4]表示第二次不持有
        dp[0][0]=0;
        dp[0][1]=-prices[0];
        dp[0][2]=0;
        dp[0][3]=-prices[0];
        dp[0][4]=0;
        for(int i=1;i<n;i++){
   
            dp[i][1]=max(dp[i-1][1],-prices[i]);
            dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i]);
            dp[i][3]=max(dp[i-1][3],dp[i-1][2]-prices[i]);
            dp[i][4]=max(dp[i-1][4],dp[i-1][3]+prices[i]);
        }
        return dp[n-1][4];
    }

188. 买卖股票的最佳时机 IV - 力扣(LeetCode)

由上一题两次的情况可以推广到k次的情况

    int maxProfit(int k, vector<int>& prices) {
   
        int n=prices.size();
        int dp[n][2*k+1];
        memset(dp,0,sizeof(dp));
        for(int i=0;i<=2*k;i++)
        {
   
            if(i%2==0) dp[0][i]=0;
            else dp[0][i]=-prices[0];
        }
        for(int i=1;i<n;i++)
        {
   
            for(int j=1;j<=2*k;j++)
            {
   
                if(j%2==0) dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]+prices[i]);
                else if(j%2!=0) dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]-prices[i]);
            }
        }
        return dp[n-1][2*k];
    }

309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)

由于具有冷冻期所以我们要明确

到底是拿一天卖出了股票所以我们把不持有股票的状态进行了拆分

int maxProfit(vector<int>& prices) {
   
        int n=prices.size();
        int dp[n][4];//dp[i][0]表示第i天持有股票
                     //dp[i][1]表示第i天保持卖出股票状态
                     //dp[i][2]表示第i天卖出股票
                     //dp[i][3]表示第i天是冷冻期
        dp[0][0]=-prices[0];
        dp[0][1]=0;
        dp[0][2]=0;
        dp[0][3]=0;
        for(int i=1;i<n;i++)
        {
   
            dp[i][0]=max(dp[i-1][0],max(dp[i-1][1]-prices[i],dp[i-1][3]-prices[i]));
            dp[i][1]=max(dp[i-1][1],dp[i-1][3]);
            dp[i][2]=dp[i-1][0]+prices[i];
            dp[i][3]=dp[i-1][2];
        }
        return max(dp[n-1][3],max(dp[n-1][2],dp[n-1][1]));
    }
目录
相关文章
|
10月前
DP刷题练习(二)
本文通过几道经典题目讲解了动态规划中的背包问题,包括0/1背包和完全背包。 **0/1背包问题**:物品只能用一次,涉及“能否装满”、“最多能装多少”、“装满有多少种方案”等变种问题。例如LeetCode 1049(石头碰撞)、494(目标和)、474(一和零)。 **完全背包问题**:物品可无限使用,关注“装满有多少种方法”或“最少需要多少物品”。如LeetCode 518(零钱兑换II)、322(零钱兑换)、279(完全平方数)。此外,还探讨了组合数与排列数的区别,以及在不同遍历顺序下的应用,如377(组合总和IV)和139(单词拆分)。
372 110
|
10月前
|
Go
【LeetCode 热题100】DP 实战进阶:最长递增子序列、乘积最大子数组、分割等和子集(力扣300 / 152/ 416 )(Go语言版)
本文深入解析三道经典的动态规划问题:**最长递增子序列(LIS)**、**乘积最大子数组** 和 **分割等和子集**。 - **300. LIS** 通过 `dp[i]` 表示以第 `i` 个元素结尾的最长递增子序列长度,支持 O(n²) 动态规划与 O(n log n) 的二分优化。 - **152. 乘积最大子数组** 利用正负数特性,同时维护最大值与最小值的状态转移方程。 - **416. 分割等和子集** 转化为 0-1 背包问题,通过布尔型 DP 实现子集和判断。 总结对比了三题的状态定义与解法技巧,并延伸至相关变种问题,助你掌握动态规划的核心思想与灵活应用!
407 1
|
10月前
|
前端开发 开发者 容器
使用CSS Grid实现响应式布局
使用CSS Grid实现响应式布局
|
10月前
|
机器学习/深度学习 人工智能 运维
|
11月前
|
SQL 关系型数据库 MySQL
菜鸟之路Day30一一MySQL之DML&DQL
本文介绍了MySQL中DML(数据操作语言)和DQL(数据查询语言)的核心用法。DML主要包括插入(insert)、更新(update)和删除(delete)语句,通过具体示例演示了如何对表数据进行增删改操作。DQL则聚焦于数据查询,涵盖基本查询、条件查询、聚合函数、分组查询、排序查询和分页查询等内容。文章通过丰富的SQL语句实例,帮助读者掌握如何高效查询和操作数据库中的数据,适合初学者学习和实践。
369 12
|
11月前
|
SQL 存储 关系型数据库
菜鸟之路Day29一一MySQL之DDL
本文《菜鸟之路Day29——MySQL之DDL》由作者blue于2025年5月2日撰写,主要介绍了MySQL中的数据定义语言(DDL)。文章详细讲解了DDL在数据库和表操作中的应用,包括数据库的查询、创建、使用与删除,以及表的创建、修改与删除。同时,文章还深入探讨了字段约束(如主键、外键、非空等)、常见数据类型(数值、字符串、日期时间类型)及表结构的查询与调整方法。通过示例代码,读者可以更好地理解并实践MySQL中DDL的相关操作。
360 11
|
弹性计算 Kubernetes 数据处理
KubeRay on ACK:更高效、更安全
阿里云 ACK 以托管组件化的方式给客户提供快速搭建Ray集群的能力,并通过结合使用阿里云的调度,存储,日志与监控,给用户提供更佳使用体验。
|
10月前
|
SQL 监控 Java
菜鸟之路Day40一一事物管理&AOP
本文主要介绍了事物管理和AOP(面向切面编程)的相关知识。在事物管理部分,详细讲解了SQL中的事物控制语句以及Spring框架中的事物注解@Transactional的使用方法和属性配置,结合删除部门及员工的实际案例说明了事物传播行为的应用场景。AOP部分首先概述了其概念和优势,如代码复用与关注点分离,并通过计算service层方法运行时间的实例展示了AOP的实现方式。接着深入探讨了AOP的核心概念,包括切面、连接点、切入点和通知类型,最后通过一个综合案例——操作日志记录到数据库中,演示了如何利用自定义注解和AOP技术完成统一功能的添加。
253 40
|
11月前
|
SQL Java 数据库连接
菜鸟之路Day34一一Mybatis-基础操作
本文介绍了MyBatis的基础操作,包括删除、插入、修改和查询功能的实现。通过`@Delete`、`@Insert`、`@Update`和`@Select`注解完成对应操作,支持主键自动生成与返回。同时探讨了`#{}`和`${}`的区别,前者用于预编译SQL提升安全性,后者直接拼接但存在SQL注入风险。文章还提供了根据ID查询及条件查询的示例,并介绍了实体类属性与数据库字段不一致时的解决方案,如使用驼峰命名规则或配置映射关系,确保数据封装准确。
324 32
|
11月前
|
SQL XML Java
菜鸟之路Day33一一Mybatis入门
本文是《菜鸟之路Day33——Mybatis入门》的教程,作者blue于2025年5月18日撰写。文章介绍了MyBatis作为一款优秀的持久层框架,如何简化JDBC开发。通过创建SpringBoot工程、数据库表`user`及实体类`User`,引入MyBatis依赖并配置数据库连接信息,使用注解方式编写SQL语句实现查询所有用户数据的功能。此外,还展示了如何通过Lombok优化实体类代码,减少冗余的getter/setter等方法,提高开发效率。最后通过单元测试验证功能的正确性。
387 19