leetcode 专题:动态规划 python 版(持续更新中)

2023-05-16

递归:自己调用自己。代码比较简洁,但是浪费空间,有许多重复计算。

迭代:利用已知的变量值,根据递推公式不断得到新的值,一直到解决问题为止。代码相对复杂一点。

递归中一定有迭代,但是迭代中不一定有递归,大部分可以相互转换。 能用迭代的不用递归,递归调用函数,浪费空间,并且递归太深容易造成堆栈的溢出。

动态规划:通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表,通常用迭代来做。

题目特点:

最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。

重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。

目录

5 最长回文子串

53 最大子序和

62 不同路径

63 不同路径 ||

64 最小路径和

70 爬楼梯

91 解码方法

120 三角行最小路径和

121 买卖股票的最佳时机

525 连续数组


 

5 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

思路:令dp[i][j]为从字符串j到i是否为回文串, dp[i][j] = s[i]==s[j] and (dp[i-1][j+1] or j-i<=2)

53 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

思路:定义一个函数f(n),以第n个数为结束点的子数列的最大和,存在一个递推关系f(n) = max(f(n-1) + A[n], A[n]);

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        dp = 0
        result = nums[0]
        for i in range(len(nums)):
            dp = max(dp+nums[i], nums[i])
            if result<dp:
                result = dp
        return result

62 不同路径

一个机器人位于一个 m x n 网格的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角)。

问总共有多少条不同的路径?

说明:m 和 的值均不超过 100。

示例 1:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

示例 2:

输入: m = 7, n = 3
输出: 28

思路:dp[i][j]表示第i行j列位置的路径个数,dp[i][j]=dp[i-1][j] + dp[i]dp[j-1]

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[0 for _ in range(n)] for _ in range(m)]
        for j in range(n): 
            dp[0][j] = 1 
        for i in range(m):
            dp[i][0] = 1
        for i in range(1,m):
            for j in range(1, n):
                dp[i][j] = dp[i][j-1]+dp[i-1][j]
        return dp[m-1][n-1]

63 不同路径 ||

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

说明:m 和 的值均不超过 100。

示例 1:

输入:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

思路:dp[i][j]表示第i行j列位置的路径个数,dp[i][j]=dp[i-1][j] + dp[i]dp[j-1], 如果[i,j]有障碍物,那么dp[i][j]=0,注意第一行和第一列,只要有障碍物,那么后面的dp都是0.

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        dp = [[0 for _ in range(n)] for _ in range(m)]
        for j in range(n):
            if obstacleGrid[0][j]==0:
                dp[0][j] = 1 
            else:
                dp[0][j] = 0
                break
        if j<n-1:
            for jj in range(j,n):
                dp[0][jj] = 0
                
        for i in range(m):
            if obstacleGrid[i][0]==0:
                dp[i][0] = 1
            else:
                dp[i][0] = 0
                break
        if i<m-1:
            for ii in range(i,m):
                dp[ii][0] = 0
                
        for i in range(1,m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 1:
                    dp[i][j] = 0
                else:
                    dp[i][j] = dp[i][j-1]+dp[i-1][j]
               
        return dp[m-1][n-1]

64 最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

思路:dp[i][j]表示第[i,j]点路径最小和,则dp[i][j] = min(dp[i-1][j]+grid[i][j], dp[i][j-1]+grid[i][j])

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        m = len(grid)
        n = len(grid[0])
        dp = [[0 for _ in range(n)] for _ in range(m)]
        dp[0][0] = grid[0][0]
        for i in range(1, m):
            dp[i][0] = grid[i][0] + dp[i-1][0]
        for j in range(1, n):
            dp[0][j] = grid[0][j] + dp[0][j-1]
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = min(dp[i-1][j]+grid[i][j], dp[i][j-1]+grid[i][j])
            
        return dp[m-1][n-1]
                

70 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶

示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶

思路:dp[i]表示第i层的方法,则dp[i] = dp[i-1]+dp[i-2]

class Solution:
    def climbStairs(self, n: int) -> int:
        if n==1:
            return 1
        if n==2:
            return 2
        dp = [0 for _ in range(n+1)]
        dp[1]=1
        dp[2]=2
        for i in range(3, n+1):
            dp[i] = dp[i-1]+dp[i-2]
        return dp[n]

91 解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:

'A' -> 1
'B' -> 2
...
'Z' -> 26

给定一个只包含数字的非空字符串,请计算解码方法的总数。

示例 1:

输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。

示例 2:

输入: "226"
输出: 3
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

dp[i]为到第i个字符的解码方法总数。正常为dp[i] = dp[i-1]+dp[i-2]。如果第i个字符不是0,那么dp[i[才可以加上dp[i-1],如果i和前面1字符是0~26之间的话,才可以加上dp[i-2]

class Solution:
    def numDecodings(self, s: str) -> int:
        length = len(s)
        if length == 0:
            return 0
        if length == 1:
            if s[0] == "0":
                return 0
            else:
                return 1
        dp = [0 for _ in range(length+1)]
        dp[0] = 1
        
        for i in range(1, length+1):
            if s[i-1]== '0':
                dp[i] = 0
            else:
                dp[i] = dp[i-1]
            if i>1 and (s[i-2]=="1" or (s[i-2]=="2" and s[i-1]<="6")):
                dp[i] += dp[i-2]
            
        return dp[-1]
        

120 三角行最小路径和

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

例如,给定三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

自顶向下的最小路径和为 11(即,3 + 1 = 11)。

说明:

如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。

思路:用一个二维数组,dp[i][j]表示下标为[i][j]的点的最小路径和。dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        rows = len(triangle)
        dp = [[0 for _ in range(rows)] for _ in range(rows)]
        for i in range(rows):
            if i==0:
                dp[0][0] = triangle[0][0]
            for j in range(len(triangle[i])):
                if i==0 and j==0:
                    dp[i][j] = triangle[0][0]
                elif j==0:
                    dp[i][j] = dp[i-1][j]+triangle[i][j]
                elif j == len(triangle[i])-1:
                    dp[i][j] = dp[i-1][j-1]+triangle[i][j]
                else:
                    
                    dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
                
        return min(dp[-1])
            

121 买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

思路:dp[i]表示前i天的最大收益,dp[i]= max(dp[i-1], prices[i]-min_price)

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        length = len(prices)
        if length == 0:
            return 0
        dp = [0 for _ in range(length)]
        min_price = prices[0]
        for i in range(length):
            if i==0:
                dp[i]=0
            else:
                min_price = min(min_price, prices[i])
                dp[i]= max(dp[i-1], prices[i]-min_price)
        return max(dp)
        

525 连续数组

给定一个二进制数组, 找到含有相同数量的 0 和 1 的最长连续子数组(的长度)。

 

示例 1:

输入: [0,1]
输出: 2
说明: [0, 1] 是具有相同数量0和1的最长连续子数组。
输入: [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。

思路:dp[i][j]为从i到j的元素的和,dp[i][j] = dp[i][j-1]+nums[j],容易超时

class Solution(object):
    def findMaxLength(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        length = len(nums)
        result = 0
        for i in range(length):
            if nums[i]==0:
                nums[i]=-1
                
        dp = [[0 for i in range(length)] for j in range(length)]
        for i in range(length):
            dp[i][i] =  nums[i]
        
        for i in range(length):
            for j in range(i+1, length):
                dp[i][j] = dp[i][j-1]+ nums[j] 
                if dp[i][j]==0 and result<j-i+1:
                    result = j-i+1
        return result
                    
        print(dp)

思路2:利用哈希表,用dic实现,key是从头开始到当前位置的和, 值为第一次出现的位置。如果该值已经出现过,那么就说明标记中的下标到当前扫描的下标的这段数组的总和值是为0的。

class Solution:
    def findMaxLength(self, nums: List[int]) -> int:
        ind, res, sm = {0:-1}, 0, 0
        for i, num in enumerate(nums):
            if num == 0:
                num = -1
            sm += num 
            if sm in ind:
                res = max(res, i - ind[sm])
            else:
                ind[sm] = i
        return res

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

leetcode 专题:动态规划 python 版(持续更新中) 的相关文章

随机推荐