动态规划(一)

5. 最长回文子串

class Solution(object):
    def longestPalindrome(self, s):
        res = ""
        if len(s) <= 1:
            return s
        for i in range(len(s)):
            s1 = self.palindrome(s, i, i)
            s2 = self.palindrome(s, i, i+1)
            res = res if len(res) >= len(s1) else s1
            res = res if len(res) >= len(s2) else s2
        return res

    def palindrome(self, s, i, j):
        if j >= len(s):
            return s[i]
        while (i >= 0 and j < len(s) and s[i] == s[j]):
            i = i-1
            j = j+1
        return s[i+1:j]

22. 括号生成

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        res = []
        def backtrace(ans, l, r):
            if l == n and r == n:
                res.append(''.join(ans))
            if l < n:
                ans.append('(')
                backtrace(ans, l+1, r)
                ans.pop()
            if r < l:
                ans.append(')')
                backtrace(ans, l, r+1)
                ans.pop()
        backtrace([], 0, 0)
        return res

32. 最长有效括号

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        # dp
        # dp[i]表示以 i 结尾的字符串最大有效长度
        dp = [0 for i in range(len(s))]
        res = 0
        if len(s) <= 1:
            return 0
        for i in range(len(s)):
            if s[i] == ')':
                # 可合并到第二个if
                if i-1 >= 0 and s[i-1] == '(':
                    dp[i] = 2 + (dp[i-2] if i>=2 else 0)
                    res = max(res, dp[i])
                    continue
                if i-1 >= 0 and s[i-1] == ')' and i-1-dp[i-1] >= 0 and s[i-1-dp[i-1]] == '(':
                    dp[i] = dp[i-1] + 2 + dp[i-2-dp[i-1]]
                    res = max(res, dp[i])
        return res

        # 栈
        # res = 0
        # stack = [-1]
        # if len(s) <= 1:
        #     return 0
        # for i in range(len(s)):
        #     if s[i] == '(':
        #         stack.append(i)
        #         continue
        #     if s[i] == ')':
        #         stack.pop()
        #         if len(stack) == 0:
        #             stack.append(i)
        #         else:
        #             res = max(res, i - stack[-1])
        # return res

42. 接雨水

class Solution:
    def trap(self, height: List[int]) -> int:
        # 双指针
        res = 0
        left = 0
        right = len(height) - 1
        l_max = r_max = 0
        while left <= right:
            l_max = max(l_max, height[left])
            r_max = max(r_max, height[right])
            if l_max <= r_max:
                res += max(0, l_max - height[left])
                left += 1
            else:
                res += max(0, r_max - height[right])
                right -= 1
        return res

        # # 备忘录优化
        # res = 0
        # l = [0 for i in range(len(height))]
        # r = [0 for i in range(len(height))]
        # l[0] = height[0]
        # r[-1] = height[-1]
        # for i in range(1, len(height)):
        #     l[i] = max(l[i-1], height[i])
        # for j in range(len(height)-2, -1, -1):
        #     r[j] = max(r[j+1], height[j])
        # for i in range(1, len(height)):
        #     res += max(min(l[i], r[i]) - height[i], 0)  # 和0取max
        # return res

        # # 暴力解法
        # res = 0
        # for i in range(len(height)):
        #     l = r = 0
        #     for j in range(i):
        #         l = max(l, height[j])
        #     for k in range(i+1, len(height)):
        #         r = max(r, height[k])
        #     res += max(min(l, r) - height[i], 0)  # 和0取max
        # return res

45. 跳跃游戏 II

class Solution:
    def jump(self, nums: List[int]) -> int:
        # 正向查找,维护当前能够到达的最大下标位置,记为边界。我们从左到右遍历数组,到达边界时,更新边界并将跳跃次数增加 1
        # 时间复杂度O(N),空间复杂度O(1)
        res = 0
        end = 0
        max_pos = 0
        for i in range(len(nums)-1):
            max_pos = max(max_pos, i + nums[i])
            if i == end:
                end = max_pos
                res += 1
        return res


        # # 贪心算法,通过局部最优求解全局最优,反向查找每次最远的可到达位置
        # # 时间复杂度 O(N),空间复杂度 O(1)
        # res = 0
        # pos = len(nums) - 1
        # while pos > 0:
        #     for i in range(len(nums)):
        #         if i + nums[i] >= pos:
        #             pos = i
        #             res += 1
        #             break
        # return res

53. 最大子数组和

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # dp
        # dp[i]表示以 nums[i]结尾的连续子数组最大和,只依赖 dp[i-1],可优化空间复杂度到 O(1)
        # 时间复杂度 O(N),空间复杂度 O(1)
        l = len(nums)
        if l < 1:
            return 0
        # dp_1 = [0 for i in range(l)]
        dp_1 = nums[0]
        res = dp_1
        for i in range(1, l):
            dp_1 = max(dp_1+nums[i], nums[i])
            res = max(res, dp_1)
        return res
        

55. 跳跃游戏

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        # 贪心,维护最远能到达位置max_pos,其中max_pos只在索引小于max_pos时更新
        l = len(nums)
        if l <= 1:
            return True
        max_pos = nums[0]
        for i in range(1, l):
            if i <= max_pos:
                max_pos = max(max_pos, i + nums[i])
                if max_pos >= l-1:
                    return True
        return False

62. 不同路径

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        # dp dp[i, j] = dp[i-1, j] + dp[i, j-1]
        # 滚动数组降低空间复杂度
        dp = [[1] * n] + [[1] + [0] * (n - 1) for _ in range(m - 1)]
        for i in range(m):
            for j in range(n):
                if i >= 1 and j >= 1:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[m-1][n-1]

63. 不同路径 II

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        # dp 滚动数组,一维数组,分行计算,每次计算时覆盖旧的
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        if m == 0 or obstacleGrid[0][0] == 1:
            return 0
        dp = [0 for _ in range(n)]
        dp[0] = 1
        for i in range(m):
            for j in range(n):
                if obstacleGrid[i][j] == 1:
                    dp[j] = 0
                elif j >= 1:
                    dp[j] = dp[j] + dp[j-1]
        return dp[n-1]

        # # dp,注意初始化 dp[0][0],首位有障碍情况
        # m = len(obstacleGrid)
        # n = len(obstacleGrid[0])
        # if m == 0 or obstacleGrid[0][0] == 1:
        #     return 0
        # dp = [[0]*n for _ in range(m)]
        # dp[0][0] = 1
        # for i in range(m):
        #     for j in range(n):
        #         if obstacleGrid[i][j] == 1:
        #             dp[i][j] = 0
        #         elif i>0 or j>0:
        #             dp[i][j] = (dp[i-1][j] if i>=1 else 0) + (dp[i][j-1] if j>=1 else 0)
        # return dp[m-1][n-1]

64. 最小路径和

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        # dp 滚动数组
        # dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
        m = len(grid)
        n = len(grid[0])
        if m == 0:
            return 0
        dp = [0 for _ in range(n)]
        dp[0] = grid[0][0]
        # 初始化
        for j in range(1, n):
            dp[j] = dp[j-1] + grid[0][j]
        # 第二行开始遍历,首列单独处理
        for i in range(1, m):
            for j in range(n):
                if j >= 1:
                    dp[j] = min(dp[j], dp[j-1]) + grid[i][j]
                else:
                    dp[j] = dp[j] + grid[i][j]
        return dp[n-1]

70. 爬楼梯

class Solution:
    def climbStairs(self, n: int) -> int:
        # dp 滚动数组
        if n <= 1:
            return 1
        dp_1 = 2
        dp_2 = 1
        dp = dp_1
        for i in range(2, n):
            dp = dp_1 + dp_2
            dp_2 = dp_1  # 先赋值dp_2
            dp_1 = dp
        return dp

        # # dp
        # if n <= 1:
        #     return 1
        # dp = [0 for _ in range(n)]
        # dp[0] = 1
        # dp[1] = 2
        # for i in range(2, n):
        #     dp[i] = dp[i-1] + dp[i-2]
        # return dp[n-1]

91. 解码方法

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

95. 不同的二叉搜索树 II

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        # 回溯,外层循环遍历 1~n 所有结点,作为根结点,内层双层递归分别求出左子树和右子树
        def generate(start, end):
            if start > end:
                return [None]
            res = []
            for i in range(start, end+1):
                leftTrees = generate(start, i-1)
                rightTrees = generate(i+1, end)
                for l in leftTrees:
                    for r in rightTrees:
                        curTree = TreeNode(i)
                        curTree.left = l
                        curTree.right = r
                        res.append(curTree)
            return res

        if n < 1:
            return []
        return generate(1, n)

96. 不同的二叉搜索树

class Solution:
    def numTrees(self, n: int) -> int:
        # dp
        # dp[n] 代表 1-n 个数能组成多少个不同的二叉排序树
        # F(i,n) 代表以 i 为根节点,1-n 个数组成的二叉排序树的不同的个数。
        # dp[n] = F(1,n) + F(2,n) + F(3,n) + …… + F(n,n) 。初始值 dp[0] = 1,dp[1] = 1
        # F(i,n) = dp[i-1] * dp[n-i]
        dp = [0 for _ in range(n+1)]
        dp[0] = 1
        dp[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[n]

97. 交错字符串

class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        # dp dp[i][j]表示s1的前i个字符和s2的前j个字符是否能组成s3的前i+j个字符
        m = len(s1)
        n = len(s2)
        if (n + m != len(s3)):
            return False
        dp = [[0]*(n+1) for _ in range(m+1)] # 注意行列
        dp[0][0] = 1
        for i in range(1, m+1):
            if s1[i-1] == s3[i-1]:
                dp[i][0] = max(dp[i][0], dp[i-1][0])
        for j in range(1, n+1):
            if s2[j-1] == s3[j-1]:
                dp[0][j] = max(dp[0][j], dp[0][j-1])
        for i in range(1, m+1):
            for j in range(1, n+1):
                if s1[i-1] == s3[i+j-1]:
                    dp[i][j] = max(dp[i][j], dp[i-1][j])
                if s2[j-1] == s3[i+j-1]:
                    dp[i][j] = max(dp[i][j], dp[i][j-1])
        print(dp)
        return dp[m][n] == 1
        
        # m = len(s1)
        # n = len(s2)
        # if (n + m != len(s3)):
        #     return False
        # dp = [[0]*(n+1) for _ in range(m+1)] # 注意行列
        # dp[0][0] = 1
        # for i in range(m+1):
        #     for j in range(n+1):
        #         if i > 0 and s1[i-1] == s3[i+j-1]:
        #             dp[i][j] = max(dp[i][j], dp[i-1][j])
        #         if j > 0 and s2[j-1] == s3[i+j-1]:
        #             dp[i][j] = max(dp[i][j], dp[i][j-1])
        # return dp[m][n] == 1

115. 不同的子序列

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        # dp dp[i][j]表示s[:j]的子序列中出现t[:i]的个数
        # 初始化,当i=0时,第一行为1;当j=0时,第一列为0
        # 转移方程:如果s[j] == t[i], dp[i][j] = dp[i-1][j-1] + dp[i][j-1]
        # 如果s[j] != t[i], dp[i][j] = dp[i][j-1]
        m = len(t)
        n = len(s)
        dp = [[0]*(n+1) for _ in range(m+1)]
        for i in range(m+1):
            dp[i][0] = 0
        for j in range(n+1):
            dp[0][j] = 1
        for i in range(1, m+1):
            for j in range(1, n+1):
                if s[j-1] == t[i-1]:
                    dp[i][j] = dp[i-1][j-1] + dp[i][j-1]
                else:
                    dp[i][j] = dp[i][j-1]
        return dp[-1][-1]
        # 交替滚动一维 dp
        # 原地滚动一维 dp,借助变量

118. 杨辉三角

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        res = list()
        for i in range(numRows):
            row = list()
            for j in range(i+1):
                if j == 0 or j == i:
                    row.append(1)
                else:
                    row.append(res[i-1][j-1] + res[i-1][j])
            res.append(row)
        return res

120. 三角形最小路径和

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        # # dp dp[i][j]表示到达triangle[i][j]的最短路径和
        # m = len(triangle)
        # n = len(triangle[-1])
        # dp = [[0]*n for _ in range(m)]
        # dp[0][0] = triangle[0][0]
        # for i in range(1, m):
        #     for j in range(i+1):
        #         if j == 0:
        #             dp[i][j] = dp[i-1][j] + triangle[i][j]
        #         elif j == i:
        #             dp[i][j] = dp[i-1][j-1] + triangle[i][j]
        #         else:
        #             dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + triangle[i][j]
        # return min(dp[m-1])

        # # 空间优化 滚动数组
        # m = len(triangle)
        # n = len(triangle[-1])
        # pre, cur = [0] * n, [0] * n
        # pre[0] = triangle[0][0]
        # cur[0] = triangle[0][0]  # 处理只有一行情况
        # for i in range(1, m):
        #     for j in range(i+1):
        #         if j == 0:
        #             cur[j] = pre[j] + triangle[i][j]
        #         elif j == i:
        #             cur[j] = pre[j-1] + triangle[i][j]
        #         else:
        #             cur[j] = min(pre[j], pre[j-1]) + triangle[i][j]
        #     pre = cur.copy()
        # return min(cur)

        # 空间优化 一维数组
        m = len(triangle)
        n = len(triangle[-1])
        cur = [0] * n
        cur[0] = triangle[0][0]
        for i in range(1, m):
            for j in range(i, -1, -1):  # 倒序,保证 cur[j-1] 未覆盖
                if j == 0:
                    cur[j] += triangle[i][j]
                elif j == i:
                    cur[j] = cur[j-1] + triangle[i][j]
                else:
                    cur[j] = min(cur[j], cur[j-1]) + triangle[i][j]
        return min(cur)

121. 买卖股票的最佳时机

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # # 一次遍历,记录到当天为止的最小股票价格和最大利润
        # n = len(prices)
        # inf = int(1e9)
        # minprice = inf
        # maxProfit = 0
        # for i in range(n):
        #     minprice = min(minprice, prices[i])
        #     maxProfit = max(maxProfit, prices[i] - minprice)
        # return maxProfit

        # dp dp[i]表示截止到第 i 天能获取的最大利润
        # 判断第i天是否卖出股票
        # dp[i] = max(dp[i-1], prices[i] - minPrice)
        # 边界条件,dp[0] = 0
        n = len(prices)
        minPrice = prices[0]
        dp = [0] * n
        for i in range(1, n):
            minPrice = min(minPrice, prices[i])
            dp[i] = max(dp[i-1], prices[i] - minPrice)
        return max(dp)

122. 买卖股票的最佳时机 II

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # # dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润
        # # dp[i][1] 表示第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)
        # # dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
        # # dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
        # n = len(prices)
        # dp = [[0]*2 for _ in range(n)]
        # dp[0][0] = 0
        # dp[0][1] = -prices[0]
        # for i in range(1, n):
        #     dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
        #     dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
        # return max([x[0] for x in dp])

        # 空间优化
        n = len(prices)
        dp_0 = 0
        dp_1 = -prices[0]
        for i in range(1, n):
            dp_temp = dp_0
            dp_0 = max(dp_0, dp_1 + prices[i])
            dp_1 = max(dp_1, dp_temp - prices[i])
        return dp_0
        
        # 贪心算法,把所有的上坡收集到就是最大利润
        n = len(prices)
        res = 0
        for i in range(1, n):
            if prices[i] - prices[i-1] > 0:
                res += prices[i] - prices[i-1]
        return res

124. 二叉树中的最大路径和

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def __init__(self):
        self.res = float("-inf")
    
    def maxPathSum(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        # 深度优先搜索, 返回当前树能贡献的最大路径值, 路径一定包含 root
        # 注意:在对每个子树进行计算时更新全局最大值
        def dfs(root: Optional[TreeNode]) -> int:
            if not root:
                return 0
            left_max = max(0, dfs(root.left))  # 左子树最大值
            right_max = max(0, dfs(root.right))  # 右子树最大值
            # 更新全局最大值, max(root, left+root, root+right, left+root+right)
            self.res = max(self.res, root.val + left_max + right_max)  # 以当前根节点为树的路径最大值
            # 返回当前根节点可向父节点贡献的路径最大值,可能是 左+根 右+根 根
            # (不能是 左+根+右,与父节点无法形成有效路径)
            return max(root.val + left_max, root.val + right_max)  
        dfs(root)
        return self.res

131. 分割回文串

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        # 回溯 + dp
        # 时间复杂度:O(n*2^n), 空间复杂度 O(n^2)
        res = []
        ans = []
        n = len(s)
        # 构建 dp 数组, dp[i][j]表示 s[i:j+1] 是否回文,(也可采用 memo 存储)
        # i >= j,  dp[i][j] = True
        # i < j, dp[i][j] = dp[i+1][j-1] and s[i] == s[j]
        dp = [[True] * n for _ in range(n)]
        for i in range(n-1, -1, -1):
            for j in range(i+1, n):
                dp[i][j] = dp[i+1][j-1] and s[i] == s[j]

        def isPalindrome(i: int, j: int) -> bool:
            return dp[i][j]

        # 递归截止条件,做选择,递归,退出选择
        def dfs(i: int):
            # 截止条件,索引超限
            if i >= n:
                res.append(ans[:])
                return
            # 做选择
            for j in range(i, n):
                if isPalindrome(i, j):
                    ans.append(s[i:j+1])
                    # 进入递归
                    dfs(j+1)
                    # 退出选择
                    ans.pop()

        dfs(0)
        return res

152. 乘积最大子数组

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        # # dp,与【最大子数组和】不一样,不满足最优子结构
        # # 需要维护两个值,以 nums[i] 结尾的最大值 & 最小值
        # n = len(nums)
        # if n == 0:
        #     return 0
        # dp_max = [0 for _ in range(n)]
        # dp_min = [0 for _ in range(n)]
        # dp_max[0] = nums[0]
        # dp_min[0] = nums[0]
        # for i in range(1, n):
        #     dp_max[i] = max(dp_max[i-1] * nums[i], dp_min[i-1] * nums[i], nums[i])
        #     dp_min[i] = min(dp_min[i-1] * nums[i], dp_max[i-1] * nums[i], nums[i])
        # return max(dp_max)

        # 空间优化
        n = len(nums)
        if n == 0:
            return 0
        dp_max = nums[0]
        dp_min = nums[0]
        res = nums[0]
        for i in range(1, n):
            tmp = dp_max
            dp_max = max(dp_max * nums[i], dp_min * nums[i], nums[i])
            dp_min = min(dp_min * nums[i], tmp * nums[i], nums[i])
            res = max(res, dp_max)
        return res
        

174. 地下城游戏

class Solution:
    def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
        # dp dp[i][j]表示要到达右下角所需要的最低初始值
        # 遍历方向需要从右下角开始,向左向上进行
        # 遇到 dp[i][j] <= 0 时,需要置为 1,保证活着
        m = len(dungeon)
        n = len(dungeon[0])
        dp = [[0]*n for _ in range(m)]
        dp[m-1][n-1] = (-dungeon[m-1][n-1] + 1) if dungeon[m-1][n-1] <= 0 else 1
        for j in range(n-2, -1, -1):
            dp[m-1][j] = max(dp[m-1][j+1] - dungeon[m-1][j], 1)
        for i in range(m-2, -1, -1):
            dp[i][n-1] = max(dp[i+1][n-1] - dungeon[i][n-1], 1)
        for i in range(m-2, -1, -1):
            for j in range(n-2, -1, -1):
                dp[i][j] = max(min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j], 1)
        return dp[0][0]

        # # dfs 暴力求解 方案超时
        # m = len(dungeon)
        # n = len(dungeon[0])

        # # dfs 返回的是 在 (i, j) 位置所需要的最小初始值
        # def dfs(i: int, j: int) -> int: 
        #     if (i == m-1 and j == n-1):
        #         return max(-dungeon[i][j] + 1, 1)
        #     r_min = dfs(i, j+1) if j <= n-2 else float('inf')
        #     d_min = dfs(i+1, j) if i <= m-2 else float('inf')
        #     res = max(min(r_min, d_min) - dungeon[i][j], 1)
        #     # print(i, j, res)  # 打印,判断重复递归
        #     return res

        # return dfs(0, 0)

        # # dfs 暴力求解 方案超时
        # m = len(dungeon)
        # n = len(dungeon[0])
        # # 增加 memo
        # memo = [[float('inf')]*n for _ in range(m)]
        # # dfs 返回的是 在 (i, j) 位置所需要的最小初始值
        # def dfs(i: int, j: int) -> int:
        #     res = 0
        #     if memo[i][j] < float('inf'):
        #         return memo[i][j]
        #     if (i == m-1 and j == n-1):
        #         res = max(-dungeon[i][j] + 1, 1)
        #     else:
        #         r_min = dfs(i, j+1) if j <= n-2 else float('inf')
        #         d_min = dfs(i+1, j) if i <= m-2 else float('inf')
        #         res = max(min(r_min, d_min) - dungeon[i][j], 1)
        #     return res

        # return dfs(0, 0)
        

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