2022.11.28 am

这篇博客总结了动态规划在解决算法题目的应用,包括斐波那契数、爬楼梯、最小花费爬楼梯、不同路径、不同路径II、整数拆分和不同的二叉搜索树等经典问题。博主详细解析了每个问题的dp状态转移方程,特别强调了01背包问题的二维和一维dp数组解法,解释了遍历顺序的重要性。
摘要由CSDN通过智能技术生成

【刷dp题目】

1.斐波那契数(509题):通过。

2.爬楼梯(70题):通过。

3.使用最小花费爬楼梯(746题):没通过。

没有明确好dp[i]的含义,到底是上楼之前花费,还是上楼之后花费。现在明确为上楼之前花费,这样的话就是dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]),dp[i]可以由dp[i - 1]和dp[i - 2]推出,从哪上来的就把哪的花费给加上(而不是加i处的花费)。第一步(也就是到达第0或第1级台阶是不花费的,因为是上楼前花费,所以dp[0]和dp[1]都是0。而且这样的定义,是到达了某层再把前面的花费给加上,所以要算到cost.size()层,也就是多一个,这样才是真正到达了,要不然相当于还没走最后一步,没到楼顶。也就是说dp数组大小为cost.size() + 1,返回dp[cost.size()]

4.不同路径(62题):通过。(一开始居然写成一个循环了,每次i++,j++,相当于斜着往下走只填了一条线啊,二维数组要每个地方都填,肯定是两层循环啊。)

5.不同路径II(63题):通过。(一开始初始化的时候有点问题,注意第一行第一列初始化的时候,只要遇到障碍就break了,后面都是0,因为走不过去了。)

6.整数拆分(343题):没通过。

没想清楚最大乘积是怎么拆分得到的。dp[i]的最大乘积是怎么得到的呢,可以把j从1开始遍历,有两种渠道得到dp[i],一是j * (i - j),二是j * dp[i - j](相当于拆分i - j)。也可以理解为,j * (i - j)是拆成两个数相乘,j * dp[i - j]是拆成两个及两个以上的数相乘。如果是dp[i] * dp[i - j]相当于强制拆成四个及四个以上的数相乘了。因此递推公式就是dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])),时刻维持一个最大乘积。初始化dp[2] = 1就行了(拆0和1没意义)。遍历j的时候可以缩小一下范围,j从1到i / 2就可以了,因为再往后之前已经拆过了。

7.不同的二叉搜索树(96题):通过(具体从1还是从2遍历,还有遍历的时候一些细节,只要把递推公式写出来能符合要求都可以)。

// 此处先复习一下01背包问题的基础知识再继续做相关题目

01背包:

有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

①用二维dp数组:dp[i][j]表示从下标为0~i的物品里任取,放进容量为j的背包,最大的价值总和。

那么可以有两个方向推出来dp[i][j],放物品i和不放物品i。递推公式就是dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])。当然如果当前物品重量比当前背包容量j还大,那只能不放i,也就是dp[i - 1][j]。

初始化的时候,dp[i][0]表示背包容量为0,那么肯定价值总和也是0,dp[0][j]表示只取物品0,所以当背包容量小于物品0的重量时,价值总和为0,大于等于物品0的重量时,价值总和为物品0的价值。

用二维数组,遍历的内外循环是随便的,然后都从小到大遍历就行,因为推导所需要的数据就是左上角的数据。

②用一维dp数组:dp[j]表示容量为j的背包,所背物品的最大价值总和。

在二维的递推公式中可以发现,推导第i行,也就是放第i个物品的时候,用到的是i - 1行的数据,如果把上一行拷贝下来重复利用就好了,所以干脆就用滚动数组。所以就是dp[j] = max(dp[j], dp[j - weight[i]] + value[i]),其实就是把i和j两个维度中的i给去掉了。然后都初始为0即可。

此时要注意遍历的内外和顺序了。

首先,遍历背包的顺序是倒序,因为要保证物品i只放入一次。

例如:物品0的重量weight[0] = 1,价值value[0] = 15。

如果正序遍历:

dp[1] = dp[1 - weight[0]] + value[0] = 15

dp[2] = dp[2 - weight[0]] + value[0] = 30

此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。

如果倒序遍历(先算dp[2]):

dp[2] = dp[2 - weight[0]] + value[0] = 15 (因为dp数组已经都初始化为0)

dp[1] = dp[1 - weight[0]] + value[0] = 15

所以从后往前循环,每次取得的状态不会和之前取得的状态重合,这样每种物品就只取一次了。实际上就是,如果正序遍历相当于先算的dp[1]把原本dp[2]的上一行(但我们给合并了)所需要的那个位置的0给覆盖了。而倒序就不会覆盖

其次,遍历的内外应该是物品在外,背包在内。

因为一维dp的写法背包容量一定是要倒序遍历,如果遍历背包容量j放在外面,那么每个dp[j]就只会放入一个物品,即背包里只放入了一个物品。(这一块其实不太好理解,可以理解为,应该一个一个物品处理,看看能不能再放下一个物品,所以物品在外。)

本质上一维数组还是相当于一个二维数组的遍历,右下角的值依赖左上角的值,因此需要保证左边的值仍然是上一层的(不能被覆盖),从右向左覆盖。

总结:一维dp数组处理01背包问题,外层遍历物品,内层遍历背包容量,并且背包容量要倒序遍历。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值