动态规划专题

139.单词拆分

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        dp = [False]*(len(s)+1)
        dp[0] = True
        for i in range(len(s)):
            for j in range(i + 1):
                if dp[j] and s[j:i+1] in wordDict:
                    dp[i + 1] = True
                    break 
        return dp[-1]

42.接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

在这里插入图片描述
动态规划:
两个数组保存对应位置往左和往右的最大高度,然后遍历高度数组,找到低的那个最大高度,减去当前位置高度得到结果。

class Solution:
    def trap(self, height: List[int]) -> int:
        if not height:
            return 0
        n = len(height)
        left_max, right_max = [0] * n, [0] * n
        left_max[0] = height[0]
        right_max[-1] = height[-1]
        ans = 0
        for i in range(1, n):
            left_max[i] = max(height[i], left_max[i - 1])
        for i in range(n - 2, -1, -1):
            right_max[i] = max(height[i], right_max[i + 1])
        for i in range(n):
            ans += min(left_max[i], right_max[i]) - height[i]
        return ans

左右指针:
左右两个指针,更新更小的位置,如果当前高度比max大,更新max,否则计算ans
每次更新时当前位置的最大值是左右两边最大的较小值,所以直接用一边的max减,每次遇到比max大,只更新,因为左边兜不住。
因为每次左边更新时右边的数组值一定比当前位置大,而且left_max存储的值也一定比right_max小,因为如果left-max比右边所有值都大,就会继续更新右边不会走到左边来。

class Solution:
    def trap(self, height: List[int]) -> int:
        n = len(height)
        left_max = right_max = ans = 0
        left, right = 0, n - 1
        while left <= right:
            if height[left] < height[right]:
                if height[left] > left_max:
                    left_max = height[left]
                else:
                    ans += left_max - height[left]
                left += 1
            else:
                if height[right] > right_max:
                    right_max = height[right]
                else:
                    ans += right_max - height[right]
                right -= 1
        return ans

413.等差数列划分

如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列。

动态规划:
dp数组保存以当前位置i为末尾的数列为等差数列的个数,因为每次相对于前一位置,多了一个三个数长度的数组,所以每次+1,最后求和得到结果。

class Solution:
    def numberOfArithmeticSlices(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [0] * n 
        for i in range(2, n):
            if (nums[i-1] - nums[i - 2] == nums[i] - nums[i - 1]):
                dp[i] = dp[i - 1] + 1
        return sum(dp)

优化:
滚动数组优化空间

class Solution:
    def numberOfArithmeticSlices(self, nums: List[int]) -> int:
        n = len(nums)
        ans = dp = 0
        for i in range(2, n):
            if (nums[i-1] - nums[i - 2] == nums[i] - nums[i - 1]):
                dp = dp + 1
                ans += dp
            else:
                dp = 0
        return ans

91.解码方法

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

‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为:

“AAJF” ,将消息分组为 (1 1 10 6)
“KJF” ,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

题目数据保证答案肯定是一个 32 位 的整数。

动态规划:
若当前位置两位为9到27之间,则加上dp[i-2],若当前位置不为0,则加上dp[i-1]

class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        dp = [0] * (n + 1)
        dp[0], dp[1] = 1, int(s[0] != '0')
        for i in range(2, n + 1):
            if s[i - 2] != '0' and 9 < int(s[i - 2: i]) < 27:
                dp[i] += dp[i - 2]
            if s[i - 1] != '0':
                dp[i] += dp[i - 1]
        return dp[-1]

优化:

class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        pre, cur = 1, int(s[0] != '0')
        for i in range(2, n + 1):
            tmp = 0
            if s[i - 2] != '0' and 9 < int(s[i - 2: i]) < 27:
                tmp = pre
            if s[i - 1] != '0':
                tmp += cur
            cur, pre = tmp, cur
        return cur

96.不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

假设 n 个节点存在二叉排序树的个数是 G (n),令 f(i) 为以 i 为根的二叉搜索树的个数,则
G ( n ) = f ( 1 ) + f ( 2 ) + f ( 3 ) + f ( 4 ) + . . . + f ( n ) G(n) = f(1) + f(2) + f(3) + f(4) + ... + f(n) G(n)=f(1)+f(2)+f(3)+f(4)+...+f(n)

当 i 为根节点时,其左子树节点个数为 i-1 个,右子树节点为 n-i,则

f ( i ) = G ( i − 1 ) ∗ G ( n − i ) f ( i ) = G ( i − 1 ) ∗ G ( n − i ) f(i) = G(i-1)*G(n-i)f(i)=G(i−1)∗G(n−i) f(i)=G(i1)G(ni)f(i)=G(i1)G(ni)

综合两个公式可以得到 卡特兰数 公式
G ( n ) = G ( 0 ) ∗ G ( n − 1 ) + G ( 1 ) ∗ ( n − 2 ) + . . . + G ( n − 1 ) ∗ G ( 0 ) G ( n ) G(n) = G(0)*G(n-1)+G(1)*(n-2)+...+G(n-1)*G(0)G(n) G(n)=G(0)G(n1)+G(1)(n2)+...+G(n1)G(0)G(n)

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

931.下降路径最小和

给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。

下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。

思路: 从下到上依次加上可以得到的最小值,边界上用min和max处理。

class Solution:
    def minFallingPathSum(self, matrix: List[List[int]]) -> int:
        while len(matrix) >= 2:
            cur = matrix.pop()
            matrix[-1] = [ matrix[-1][i] + min(cur[max(0, i - 1): min(i + 2, len(cur))]) for i in range(len(cur))]
        return min(matrix[0])

120.三角形最短路径和

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

类似上题

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        while len(triangle) >= 2:
            cur = triangle.pop()
            triangle[-1] = [triangle[-1][i] + min(cur[i: min(i + 2, len(cur))]) for i in range(len(triangle[-1]))]
        return triangle[0][0]

1314. 矩阵区域和

给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和:

i - k <= r <= i + k,
j - k <= c <= j + k 且
(r, c) 在矩阵内。

利用前缀和数组计算。

class Solution:
    def matrixBlockSum(self, mat: List[List[int]], k: int) -> List[List[int]]:
        m, n = len(mat), len(mat[0])
        proSum = [[0] * (n + 1) for _ in range(m + 1)]
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                proSum[i][j] = proSum[i-1][j] + proSum[i][j-1] - proSum[i-1][j-1] + mat[i - 1][j - 1]
        def get(i, j):
            i, j = max(0, min(i, m)), max(0, min(j, n))
            return proSum[i][j]
        
        for i in range(m):
            for j in range(n):
                mat[i][j] = get(i+k+1, j+k+1) - get(i-k, j+k+1) - get(i+k+1, j-k) + get(i-k, j-k)
        return mat
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值