【DP-03】动态规划算法题目解析

目录

  1. 面试题14- I. 剪绳子/343. 整数拆分
  2. 面试题42. 连续子数组的最大和/53. 最大子序和
  3. 面试题47. 礼物的最大价值

一、面试题14- I. 剪绳子/343. 整数拆分

1.1 问题:

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:

输入: 2

输出: 1

解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:

输入: 10

输出: 36

解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

1.2 求解:

1)步骤一:定义子问题

假设n的最大值为f(n),可以将n拆分成i和n-i,问题变成 i乘以f(n-i),这样就变成了f(k),k<n的子问题了。

2)写出子问题的递推关系

和第一步一样,使用dp表示其值,但是要注意 i * (n - i) 本省也可能成为最大值;另外因为是遍历循环,要考虑计算过的dp[i],因此得到如下表达式:

dp[i] = max(dp[i], max(i * dp(n - i),i * (n - i)))

初始值:每个dp都设置为1

3)确定 DP 数组的计算顺序

而这里的 n 是什么呢?我们说了dp自底向下的思考方式。因此这里的 n 实际上是 1,2,3,4... n

自然地,我们用一层循环来生成上面一系列的 n 值。接着我们还要生成一系列的 i 值,注意到 n - i 是要大于 0 的,因此 i 只需要循环到 n - 1 即可。

4 )空间优化(可选)

因为要循环使用,所以无法进行空间优化

1.3 代码

class Solution:

    def integerBreak(self, n: int) -> int:

        dp=[1]*(n+1)

        for i in range(3,n+1):

            for j in range(1,i):

                dp[i] = max(j*dp[i-j],j*(i-j),dp[i])

        return dp[-1]

二、面试题42. 连续子数组的最大和/53. 最大子序和

2.1 问题:

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]

输出: 6

解释连续子数组 [4,-1,2,1] 的和最大,为 6

2.2 求解:

问题比较简单,一维数组的问题。

1)步骤一:定义子问题

假设n的最大值为f(n),可以将n拆分成i和n-i,问题变成 i加上f(n-i)或直接是f(n-i),这样就变成了f(k),k<n的子问题了

2)写出子问题的递推关系

上面的进行简化:

初始状态:将每个复制为0或数组的第一个值。

3)确定 DP 数组的计算顺序

很明显,使用自底向上的方式进行。具体代码见2.3的代码一

4 )空间优化(可选)

从代码也很明显看到,只用了dp[i-1]这个前置项,另外是最后输出的max(dp)也需要保留,其它是暂存也没有用到,所以可将其优化,将dp[i-1]max_temp,最后的输出使用res。具体可见代码二。

2.3 代码

代码一:

class Solution:

    def maxSubArray(self, nums: List[int]) -> int:

        n = len(nums)

        if n == 0:return 0

        else:

            dp = [nums[0]] *(n)

            for i in range(1,n): #注意这里不是n+1

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

            return max(dp)

代码二:

class Solution:

    def maxSubArray(self, nums: List[int]) -> int:

        n = len(nums)

        if n == 0:return 0

        else:

            max_temp = nums[0]

            res = max_temp

            for i in range(1,n):

                max_temp = max (max_temp + nums[i],nums[i])

                res = max(res,max_temp)

            return res

三、面试题47. 礼物的最大价值

3.1 问题:

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:

输入:

[

  [1,3,1],

  [1,5,1],

  [4,2,1]

]

输出: 12

解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

3.2 求解:

是二维数组题,难度为中等偏上,和这题【1143. 最长公共子序列】类似

1)步骤一:定义子问题

题目说明:从棋盘的左上角开始拿格子里的礼物,并每次 向右 或者 向下 移动一格、直到到达棋盘的右下角。

根据题目说明,易得某单元格只可能从上边单元格或左边单元格到达。

很明显,缩小规模的子问题和原文题是等效的。

2)写出子问题的递推关系

定义二维矩阵,设动态规划矩阵 dp dp(i,j) 代表从棋盘的左上角开始,到达单元格 (i,j) 时能拿到礼物的最大累计价值。

3)确定 DP 数组的计算顺序

先行后列,从上到下进行计算即可。具体代码见3.3代码一。

4 )空间优化(可选)

对角进行替换,换成下图所示的例子,进一步优化空间:

  

temp

dp[j-1]

dp[j-1]

只是用上面和左边的,所以可以重复利用,循环推进就可以了。具体见代码二。

3.3 代码

代码一:

class Solution:

    def maxValue(self, grid: List[List[int]]) -> int:

        m = len(grid)

        n = len(grid[0])

        dp = [[0]*(n+1for _ in range(m+1)]   #(m+1)(n+1)的矩阵

        for i in range (1,m+1):   #注意要从1开始,结束为m+1

            for j in range(1,n+1):

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

        return dp[-1][-1]

代码二:

class Solution:

    def maxValue(self, grid: List[List[int]]) -> int:

        m = len(grid)

        n = len(grid[0])

        dp = [0]*(n+1)    #(n+1)的数组

        for i in range (1,m+1):   #注意要从1开始,结束为m+1

            for j in range(1,n+1):

                temp = dp[j] #这个也可省略,因为是循环更新,可以代表d[i-1][j]

                dp[j] = max (temp,dp[j-1]) + grid[i-1][j-1]

        return dp[-1]

参考文章:

1https://leetcode-cn.com/

posted @ 2020-06-02 23:16  忆凡人生  阅读(318)  评论(0编辑  收藏  举报