leetcode 每日一题复盘(线性dp)

leetcode 746 使用最小花费爬楼梯

445f575aac394921bf4e73c0fa815d6d.png

虽然是简单题但还是要说一下,感觉做题的思路还是不够清晰,好的是知道状态是最低花费,知道围绕所求的目标进行展开,倒推出递推公式

一开始写的递推公式是dp[i]=dp[i-1]+min(cost[i-2],cost[i-1]),写出了一个类似贪心算法的东西,归根结底还是对dp状态的认识不过,不清楚i代表什么,dp代表什么

实际上dp代表的是到达某一层的最低花费,而i则是层数,上面就是对i的代表不明确导致的,正确的推导公式应该是dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]),选择从i-1层和i-2层到达i层两者的最小值

其他的没什么好讲的,也就是题目表达的不是很好,有点难理解,需要跨越数组的最后一个元素(下标为n)才是到达楼顶而不是到达最后一个元素(下标n-1) 

因此我们最后取dp的下标是n,也就是数组cost的长度(最后一个元素的下一位)

leetcode 343 整数拆分

15871b5599434105a7c55d4187faebe2.jpg

be5f16f68b41416084119e5d1f797886.jpg

一直想不出来dp数组怎么递推,这也是这道题的难点,后来想用数学推导的方法解决(因为知道两个数相等时乘积最大),但是最后也没解决出来

这道题为什么难呢?因为有多种状态,你可以分成2个数相乘也可以分成3,4个数相乘,状态很多就难以确定哪个是最大的,dp的前一个状态就难以确定,就不要说求dp的状态了

dp递推公式难以确定,如果我们要求dp[n],一般会用到dp[n-1],dp[n-1]怎么样才能变成dp[n]呢,一开始我们很难想清楚

n是我们要拆分的数字,如何进行拆分:可以分成拆分成两个数字和两个数字以上两种

分成两个数字的,dp[n]=(n-j)*j,j代表n分出来的数字

分成两个数字以上的,dp[n]=dp[n-j]*j,dp[n-j]代表n-j的拆分数字的乘积组合

因为我们要求dp[n],我们需要从dp[3]开始递推(0,1,2只有一种情况,已经确定值大小),因此设i=3开始遍历构造dp数组,设j为i分出来的数字,因此j需要小于i

那么回到递推公式,递推公式应该怎么写呢,dp[n]=max(dp[n-j]*j,(n-j)*j)?,这样想就太简单了,我们还需要在max外层再套一层max用来keep住最大值,而不是简单的比较j这个情况两者的最大值


如果是第一次做这题,感觉还是不好做的(也许是我笨),面对复杂情况,无法简单得出递推公式时,可以尝试分类简化,抓住本质(所乘的数都是从n拆分出来的)

leetcode 96 不同的二叉搜索树

看题目好像挺简单,但是实际上做起来的时候让人摸不着头脑,dp的上一个状态怎么和下一个状态联系起来呢,也是这一题的难点

首先需要明确的是dp代表的状态是n个节点的二叉搜索树的数量

我们很容易可以得知dp[3]=5,也就是3个节点时二叉树有5种,那么怎么求dp[4]呢,怎么将dp[4]和dp[3]联系起来呢,答案是无法直接联系的,现在的题目情况复杂,已经无法直接从上一个状态得出下一个状态了,必须结合之前所有的状态综合考虑

对于复杂情况,我们可以进行分类,将以哪个数字为根作为依据进行分类,那么dp[4]可以分成4类分别是1,2,3,4

1为根时,其右子树有三个元素,左子树没有元素即dp[3]*dp[0]

2为根时,其右子树有两个元素,左子树有一个元素即dp[2]*dp[1]

3为根时,其右子树有一个元素,左子树有两个元素即dp[1]*dp[2]

4为根时,其右子树没有元素,左子树有三个元素即dp[0]*dp[3]

几种情况加起来就是dp[4]

有些同学可能会疑惑为什么是乘而不是加呢,因为当右子树里有多种情况,左子树里也有多种情况时我们需要用乘法才能将每一种情况都完全列出来

明白了上面这个例子就可以写递推公式啦,用两层for循环遍历,一层确定要求的dp一层确定根的数字的分布情况

dp[i]+=dp[j-1]*dp[i-j]//j-1表示j为根时的左子树的数字数量(j-1表示除去根之外比根小的),i-j表示j为根时右子树的数字数量,i也就是一共有多少个数字

leetcode 416 分割等和子集

01背包问题,一个字:难 刚学背包问题先写一个二维数组的版本理解一下先

第一步确定dp数组:把target(数组和一半)作为背包容量,作为j;把数组中的元素作为物品的大小,同样也是物品的值,作为i;dp数组表示数组元素的和

第二部确定递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i-1]] + nums[i-1]);推导过程就不细说,也就是分成两类,放i和不放i的,放i的时候还要用max比较取最大值

也许有的同学会疑惑为什么不是dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);呢?因为这里j=0时是表示容量为0,i=0时表示物品大小为零,所以我们的数组的第0行和第0列都是不使用的,我们初始化的数组是target+1,size+1把第0行列空出来
而有的数组是使用第0行,列的所以递推公式中的下标有所不同,需要因题而异

这里为啥是nums[i-1]呢,不是把下标为i的元素加上吗?一开始我也很疑惑,答案是因为我们初始化dp数组的时候长度是要比nums大1的,因此直接用i的话会造成偏移,所以我们需要i-1表示正确的下标,当然我们也可以自己检查一下,把i代进去看看就会发现数组越界了

一般遍历物品数组的时候判断条件是不会等于的,都是因为数组初始化造成的变化

这里补回一个一维数组的版本,一维数组写起来确实简洁,效率又高,熟悉了之后确实好用 

### 回答1: 这句话的意思是,在顺序表中取出第i个元素所需要的时间与i的大小成正比。也就是说,取出第一个元素所需的时间最短,取出第二个元素所需的时间比第一个元素长一些,以此类推,取出第i个元素所需的时间比取出第i-1个元素所需的时间长一些,但是两者之间的比例是相同的。 ### 回答2: 顺序表是一种以数组为基础实现的数据结构,其中元素按照顺序依次存储。如果要取出第i个元素,只需要通过数组的下标即可访问到该元素,即在O(1)的时间内完成操作。 但是,根据题目所给出的假设,在顺序表中取出第i个元素所花费的时间与i成正比。这意味着,随着i的增加,访问该元素所需的时间也会增加。 这个假设是有一定的理论依据的。因为在顺序表中,元素是依次存储的,越靠后的元素需要经过的“跳跃步数”越多,因此访问时间也会更长。另外,由于计算机内存的读写操作速度是有限的,所以在不同位置上的存储访问速度也会出现差异。 在实际应用中,虽然访问顺序表中的元素所需的时间可能会有差异,但是由于计算机的处理速度非常快,通常不会对程序的性能产生明显的影响。因此,我们可以在使用顺序表时忽略这个假设,将访问时间视作常量,进而简化运算和分析过程。 ### 回答3: 顺序表是一种数据结构,它通过数组实现,每个元素在内存中连续存储,可以随机访问。在顺序表中,元素的位置是按照固定的顺序存储的,通过下标可以直接访问到该元素。 在顺序表中取出第i个元素所花费的时间与i成正比,这是因为当需要访问第i个元素时,需要先访问前面i-1个元素。因为顺序表中元素在内存中的存储位置是连续的,所以每次访问一个元素都需要顺序遍历前面的元素来找到需要访问的元素,因此时间复杂度是O(i)。 假设顺序表有n个元素,要遍历整个顺序表,需要的时间复杂度是O(n),这是一种线性时间复杂度,因为需要遍历的元素个数和顺序表的大小成正比。但是,如果只访问顺序表中的某个元素,时间复杂度是常数级别的O(1),因为只需要一个下标就可以直接访问该元素,不需要遍历整个顺序表。 因此,在使用顺序表时,需要注意到对于某些操作,时间复杂度可能会比较高,需要采用其他的数据结构或者算法来优化。同时,在设计顺序表的时候,也需要考虑到元素的访问顺序,把最常访问的元素放在最前面可以提高访问效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值