LeetCode之动态规划实战之最大子序和(53)、爬楼梯(70)、打家劫舍(198)

动态规划

  • 1、最大子序和(53)
  • 2、爬楼梯(70)
  • 3、打家劫舍(198)

1、最大子序和(53)

题目描述:

【简单题】

LeetCode之动态规划实战之最大子序和(53)、爬楼梯(70)、打家劫舍(198)_第1张图片

题目链接
思路分析

题解二:动态规划

第1步:定义状态

既然一个连续子数组一定要以一个数作为结尾,那么我们可以将状态定义成如下:

dp[i]:表示以num[i]结尾的连续子数组的最大和

第2步:状态转移方程

根据状态的定义,由于 nums[i] 一定会被选取,并且 dp[i] 所表示的连续子序列与 dp[i - 1] 所表示的连续子序列(有可能)就差一个 nums[i]

假设数组 nums 全是正数,那么一定有 dp[i] = dp[i - 1] + nums[i],但是搞不好 dp[i - 1] 是负数也是有可能的。例如前几个数都是负数,突然来了一个正数。

于是分类讨论:

  • 如果 dp[i - 1]>= 0,那么可以把 nums[i] 直接接在 dp[i - 1] 表示的那个数组的后面。

  • 如果 dp[i - 1] < 0,那么加上前面的数反而越来越小了,于是“另起炉灶”,单独的一个 nums[i],就是 dp[i]

以上两种情况的最大值就是 dp[i] 的值,写出如下状态转移方程:
d p [ i ] = { d p [ i − 1 ] + n u m s [ i ] ,   i f   d p [ i − 1 ] ≥ 0 n u m s [ i ] ,   i f   d p [ i − 1 ] < 0 dp[i]=\left\{ \begin{aligned} dp[i-1]+nums[i],\ if\ dp[i-1]\geq0 \\ nums[i],\ if\ dp[i-1]<0 \end{aligned} \right. dp[i]={dp[i1]+nums[i], if dp[i1]0nums[i], if dp[i1]<0

记为“状态转移方程 1”。

状态转移方程还可以这样写,反正求的是最大值,也不用分类讨论了,就这两种情况,取最大即可,因此还可以写出状态转移方程如下:

d p [ i ] = m a x { n u m s [ i ] , d p [ i − 1 ] + n u m s [ i ] } dp[i]=max\{nums[i],dp[i−1]+nums[i]\} dp[i]=max{nums[i],dp[i1]+nums[i]}

记为“状态转移方程 2”。

动态规划的问题经常要分类讨论,这是因为动态规划的问题本来就有最优子结构的特征,即大问题的最优解通常由小问题的最优解得到,那么我们就需要通过分类讨论,得到大问题的小问题究竟是哪些。

第3步:初始值

dp[0] 根据定义,一定以 nums[0] 结尾,因此 dp[0] = nums[0]

第4步:思考输出

这里状态的定义不是题目中的问题的定义,不能直接将最后一个状态返回回去。

输出应该是把所有的 dp[0]、dp[1]、……、dp[n - 1] 都看一遍,取最大值。

【代码实现】

#状态转移方程1
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n=len(nums)
        if n==0:
            return
        dp=[0 for _ in range(n)]
        dp[0]=nums[0]
        for i in range(1,n):
            if dp[i-1]>=0:
                dp[i]=dp[i-1]+nums[i]
            else:
                dp[i]=nums[i]
        return max(dp)
#状态转移方程2
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n=len(nums)
        if n==0:
            return
        dp=[0 for _ in range(n)]
        dp[0]=nums[0]
        for i in range(1,n):
            dp[i]=max(dp[i-1]+nums[i],nums[i])
           
        return max(dp)

  • 时间复杂度: O ( N ) O(N) O(N)
  • 空间复杂度: O ( N ) O(N) O(N)

第5步:思考状态压缩

既然当前状态只与上一个状态有关,我们可以将每一个状态的值保存在一个表中,就可以将空间复杂度压缩到 O ( 1 ) O(1) O(1)

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n=len(nums)
        if n==0:
            return
        pre=nums[0]#pre移动指针,表示上一个状态的值
        res=pre
        for i in range(1,n):
            pre=max(nums[i],pre+nums[i])
            res=max(res,pre)
        return res
  • 时间复杂度: O ( N ) O(N) O(N)
  • 空间复杂度: O ( 1 ) O(1) O(1)

2、爬楼梯(70)

之前博文

3、打家劫舍(198)

题目描述:

【简单题】

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

LeetCode之动态规划实战之最大子序和(53)、爬楼梯(70)、打家劫舍(198)_第2张图片
题目链接

思路分析

1、由题意可知此题(最高金额)是个最优问题,可以尝试使用动态规划法求解。

2、限制条件:不能连续的偷两家

3、最后能偷到的最大金额,最后一家偷还是不偷呢?这取决于截至倒数第2家能偷到的最大金额以及截至倒数第三家能偷到的最多的金额。如果最后一家的钱加上截至倒数第三家的偷的总金额比截至倒数第二家能偷到的最多金额大,那么小偷就选择倒数第二家不偷。否则不如放弃最后一家,拿着截至倒数第二家的金额走。

4、所以存在一个最优子结构,即要想得到截至第i家最多能偷多少钱,就得先知道截至第 i − 1 i-1 i1与截至第 i − 2 i-2 i2家最多能偷多少钱。由此以来,可定义状态:

第1步:定义状态

dp[i]:表示截至第i家,能偷的最多的钱。

第2步:状态转移方程
dp[i]=max(dp[i-1],dp[i-2]+nums[i])

第3步:初始值(边界条件)

dp[0]=nums[0] 只有一间房屋,则偷窃该房屋
dp[1]=max(nums[0],nums[1]) 只有两间房屋,选择其中金额较高的房屋进行偷窃

第4步:思考输出

根据状态的定义,输出答案应为 dp[n-1],其中 n 是数组的长度。结合前面的分析,实现代码。

class Solution:
    def rob(self, nums: List[int]) -> int:
        
        n=len(nums)
        dp=[0]*n
        if n==0: #这个不要忘了
            return 0
        if n==1:
            return nums[0]
        dp[0]=nums[0]
        dp[1]=max(nums[0],nums[1])
        for i in range(2,n):
            dp[i]=max(dp[i-1],dp[i-2]+nums[i])
        return dp[n-1]#索引从零开始的原因
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

第5步:思考状态压缩

考虑到每间房屋的最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组(两个辅助变量),在每个时刻只需要存储前两间房屋的最高总金额。

class Solution:
    def rob(self, nums: List[int]) -> int:
        
        n=len(nums)
        dp=[0]*n
        if n==0: #这个不要忘了
            return 0
        if n==1:
            return nums[0]
        first,second=nums[0],max(nums[0],nums[1])
        for i in range(2,n):
            first,second=second,max(first+nums[i],second)
        return second
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

你可能感兴趣的:(leetcode,动态规划,leetcode)