leetcode动态规划

目录

  1. 最低票价
  2. 最长回文子串
  3. 最大子序和
  4. 不同路径 II
  5. 最小路径和
  6. 最大矩形
  7. 不同的二叉搜索树 II
  8. 不同的二叉搜索树
  9. 三角形最小路径和
  10. 乘积最大子序列
  11. 最大正方形
  12. 最长上升子序列
  13. 零钱兑换
  14. 打家劫舍 II
  15. 分割等和子集
  16. 最后一块石头的重量 II
  17. 完全平方数

暴力递归

暴力递归就是尝试:

  1. 把问题转化为规模缩小的同类问题的子问题
  2. 有明确的不需要继续进行递归的条件(base case)
  3. 有当得到了子问题的结果之后的决策过程
  4. 不记录每一个子问题的解

递归中出现重复计算时,为了快捷需要将算过的值保存到缓存表里

动态规划模型:
● 从左向右尝试模型
● 范围尝试模型: [ L, R ]: 范围尝试模型特别讨论开头、结尾情况如何如何
● 样本对应模型: 一个样本做行,一个样本做列的样本对应模型

在这里插入图片描述

def ways(N,M,K,P):
  return process(M,K,P,N)

# 递归函数: 
	#	cur: 表示机器人当前的位置, rest: 表示还剩余几步可以走, p为目标位置,N为位置总数
  # 这里的状态变量就是: cur 和rest
def process(cur,rest,P,M):
  # basecase: rest=0 表示机器人已经走完所有的步数
  if rest==0:
    return 1 if cur == P else 0
  # 如果rest> 0 表示还有步数要走
  if cur==1:
    return process(2,rest-1,P,M)
  elif cur ==N:
    return process(N-1,rest-1,P,M)
  else:
    return process(cur-1,rest-1,P,M) + process(cur+1,rest-1,P,M)
 
# 动态规划 
def walkways(N, M, K, P):
    m = [[0 for i in range(N + 1)] for j in range(K + 1)]
    
    # 初始化
    m[0][P] = 1

    # 求解
    for i in range(1, K+1):
        for j in range(1, N+1):
            if j == 1:
                m[i][j] = m[i-1][j+1]
            elif j == N:
                m[i][j] = m[i-1][j-1]
            else:
                # print('i ={0} , j= {1}'.format(i, j))
                m[i][j] = m[i-1][j-1] + m[i-1][j+1]
            
    return m[K][M]

在这里插入图片描述

def winer(arr):
  n = len(arr)
  return max(f(0,n-1,arr), g(0,n-1,arr))

# 先手获得最好分数返回
def f(l,r, arr):              ===> f两个状态变量
  # basecase情况 l==r
  if l== r:
    return arr[l]
  return max(arr[l]+g(l+1,r,arr),arr[r]+g(l,r-1,arr))
  
def g(l,r,arr):               ===> g两个状态变量
  # basecase情况 l==r
  if l== r:
    return 0
  return min(f(l+1,r,arr), f(l,r-1,arr)) # 对手拿走某张纸牌,剩余的纸牌中为先手
def winer3(arr):
  n = len(arr)
  fdp = [[0 for _ in range(n)] for _ in range(n)] # 先手函数的缓存表
  gdp = [[0 for _ in range(n)] for _ in range(n)] # 后手函数的缓存表
  
  for i in range(n):
    fdp[i][i] =arr[i]
    
  for i in range(1,n):
    l=0
    r=i
    while r <n:
      fdp[l][r] = max(arr[l]+gdp[l+1][r],arr[r]+gdp[l][r-1])  # 将递归改为从表中拿值
    	gdp[l][r] = min(fdp[l+1][r], fdp[l][r-1])
      l+=1
      r+=1
  
  return max(fdp[0][n-1], gdp[0][n-1])

背包问题: 动态规划优化

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

# 初始化,先全部赋值为0,这样至少体积为0或者不选任何物品的时候是满足要求  
dp = [[0 for i in range(v+1)] for j in range(n+1)]

for i in range(1, n+1):
    for j in range(1,v+1):
        dp[i][j] = dp[i-1][j]  # 第i个物品不选
        if j>=goods[i-1][0]:# 判断背包容量是不是大于第i件物品的体积
            # 在选和不选的情况中选出最大值
            dp[i][j] = max(dp[i][j], dp[i-1][j-goods[i-1][0]]+goods[i-1][1])


print(dp[-1][-1])

  1. 解码方法
    在这里插入图片描述
def number(str):
  return process(str,0)

# 从左向右模型
# str[0: i-1]转化无需过问.str[i:]返回有转化转化方法
def process(str,i):
  if i = len(str):  # basecase 模型的右边界, i走到了最后,没有字符可以转化
    return 1   # 表示之前的转化有效
  # i没到最后,说明有字符
  if str[i]=='0':     # 无法转换
    return 0
  # 可能性:1. i位置元素单转 2. i位置和i+1位置一起转
  ans =process(str,i+1)
  if(i+1 < len(str)) and (str[i]-'0')*10 +(str[i+1]-'0')<27:
    ans += process(str,i+2) 
 	return ans
  
  

动态规划优化:

class Solution:
    def numDecodings(self, s: str) -> int:
    

        dp=[0] * (len(s)+1) # dp[i]表示字符串前i个字符的解码方法数
        dp[0] = 1
        for i in range(1,len(s)+1): 
            if s[i-1] !='0':   # 仅使用了一个字符
                dp[i] += dp[i-1]
            if i>1 and s[i-2] !='0' and int(s[i-2:i])<=26:   #使用两个字符解码
                dp[i] +=dp[i-2]
        return dp[-1]

1143. 最长公共子序列

动态规划:

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        # 其中 dp[i][j] 表示 text1[0:i] 和 text2[0:j] 的最长公共子序列的长度
        # 空字符串和任何字符串的最长公共子序列的长度都是 0,dp[0][j]=0。dp[i][0]=0

        m, n = len(text1), len(text2)
        dp = [[0] * (n + 1) for _ in range(m + 1)]
        
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if text1[i - 1] == text2[j - 1]:  # 这里的i,j表示的是字符串的长度,而非index
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
        
        return dp[m][n]

516. 最长回文子序列

在这里插入图片描述

class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        n = len(s)
        dp = [[0] * n for _ in range(n)]
        # dp[i][j]表示字符串s的下标范围为[i,j]内的最长回文子序列的长度,由于任何长度为1的子序列都是回文子序列
        # 故dp[i][i] = 1
        for i in range(n-1,-1,-1): # 为什么要倒序,因为由于状态转移方程都是从长度较短的子序列向长度较长的子序列转移,因此需要注意动态规划的循环顺序。 较短的子序列是i +1到j-1开始计算的
            dp[i][i] = 1
            for j in range(i + 1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i + 1][j - 1] + 2
                else:
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
        return dp[0][n - 1]

在这里插入图片描述
样本对应模型:

def jump(a,b,k):
  process(0,0,a,b,k)

def process(x,y,a,b,rest):
  if x<0 or x>9 or y<0 or y>8:  # 跳出越界,无效
    return 0
  if k ==0:
    if x==a and y==b:
      return 1
    else :
      return 0
  return process(x+2,y+1,rest-1,a,b)+process(x+1,y+2,rest-1,a,b)+
process(x-1,y+2,rest-1,a,b)+process(x-2,y+1,rest-1,a,b)
+process(x-2,y-1,rest-1,a,b)+process(x-1,y-2,rest-1,a,b)
+process(x+1,y-2,rest-1,a,b)+process(x+2,y-1,rest-1,a,b)
 

动态规划:

def jump(a,b,k):
  dp = [[[0 for _ in range(a)] for _ in range(b)] for _ in range(k+1)]
  dp[a][b][0]=1
  for rest in range(1,k+1):
    for x in range(a):
      for y in range(b):
        dp[i][j][k]= dp[x+2][y+1][rest-1]+dp[x+1][y+2][rest-1]+
        dp[x-1][y+2][rest-1]+dp[x-2][y+1][rest-1]
        +dp[x-2][y-1][rest-1]+dp[x-1][y-2][rest-1]
        +dp[x+1][y-2][rest-1]+dp[x+2][y-1][rest-1]
    
  return  dp[0][0][k]

983. 最低票价
在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有三种不同的销售方式:

一张为期一天的通行证售价为 costs[0] 美元;
一张为期七天的通行证售价为 costs[1] 美元;
一张为期三十天的通行证售价为 costs[2] 美元。
通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。

解题思路:
将days数组展开为men[366]的数组。
我们定义函数

  1. f(i)=f(i-1) if i not in days
  2. else f(i)=min(f(i-1)+costs[0],f(i-7)+costs[1],f(i-30) +costs[2])

代码如下:

class Solution(object):
    def mincostTickets(self, days, costs):
        """
        :type days: List[int]
        :type costs: List[int]
        :rtype: int
        """
        men=[0]*366
        for i in range(days[0],days[-1]+1):
            if i not in days:
                men[i]=men[i-1]
            else:
                men[i]=min(men[i-1]+costs[0],men[i-7]+costs[1],men[i-30]+costs[2])      #当 1-7,i-30 <0时,数组性质
            
        return men[days[-1]]

5. 最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。

代码如下:

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        n = len(s)
        maxl = 0    #用于储存第一个最大子串的长度,后面的子串筛选自动省略小于等于maxl的子串
        start = 0     #储存第一个最大子串的起始位置。
        for i in range(n):
        	# i-maxl>=1 表示可以考虑:i-maxl-1的位置和i两个位置元素加进去考虑是否回文的情况
            if i - maxl >= 1 and s[i-maxl-1: i+1] == s[i-maxl-1: i+1][::-1]:           #添后加s[i]子字符串头尾相同的情况
                start = i - maxl - 1
                maxl += 2
                continue
            if i - maxl >= 0 and s[i-maxl: i+1] == s[i-maxl: i+1][::-1]:           #头尾不同时的情况
                start = i - maxl
                maxl += 1
        return s[start: start + maxl]

动态规划

class Solution:
    def longestPalindrome(self, s: str) -> str:
        # 我们用 P(i,j) 表示字符串 s 的第 i 到 j 个字母组成的串(下文表示成 s[i:j])是否为回文串:
        n = len(s)
        if n < 2:
            return s
        
        max_len = 1
        begin = 0
        # dp[i][j] 表示 s[i..j] 是否是回文串
        dp = [[False] * n for _ in range(n)]
        for i in range(n):
            dp[i][i] = True
        
        # 递推开始
        # 先枚举子串长度
        for L in range(2, n + 1):
            # 枚举左边界,左边界的上限设置可以宽松一些
            for i in range(n):
                # 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                j = L + i - 1
                # 如果右边界越界,就可以退出当前循环
                if j >= n:
                    break
                    
                if s[i] != s[j]:
                    dp[i][j] = False 
                else:
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]
                
                # 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
        return s[begin:begin + max_len]

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

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if len(nums)<1:
            return
        n = len(nums)-1
        ans = []
        def process(i):
            if i==0:
                ans.append(nums[i])
                return nums[i]
            p1 = nums[i]+process(i-1)
            p2 = nums[i]
            ans.append(max(p1,p2))
            return max(p1,p2) 
        process(n)
        return max(ans)
        

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

解题思路:利用动态规划的方法,从索引1开始,当他加上前面的元素后,比较该和与当前元素的大小,哪个大留下哪个。最后取数组中最大的值即为最大子集和。寻找最优子结构

def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        array=[nums[0]]*len(nums)   #开辟额外数组   array[i]表示前i个元素中所有添加array[i]形成的最大子序和
        max_sum=nums[0]
        for i in range(1,len(nums)):
            array[i]= max(array[i-1]+nums[i],nums[i])   #加入元素nums[i]后,当前点产生的最大子序和   
            max_sum=max(max_sum,array[i])          #定位最大值
            
        return max_sum

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

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

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

在这里插入图片描述
解题思路:构造全0矩阵
代码如下:

 def uniquePathsWithObstacles(self, obstacleGrid):
        # 我们用 f(i,j) 来表示从坐标 (0,0) 到坐标 (i,j) 的路径总数,u(i,j) 表示坐标 (i,j) 是否可行,如果坐标 (i,j) 有障碍物,u(i,j)=0,否则 u(i,j)=1。
        m=len(obstacleGrid)                            #not mat but list
        n=len(obstacleGrid[0])
        matrix=[[0 for _ in range(n)]for _ in range(m)]
        if obstacleGrid[0][0]==1:                     #自底向上求解
            return matrix[-1][-1]
        else:
            matrix[0][0]=1
        for i in range(m):
            for j in range(n): 
                if obstacleGrid[i][j]==1:
                    continue
                if i!=0:
                    matrix[i][j]+=matrix[i-1][j]
                if j!=0:
                    matrix[i][j]+=matrix[i][j-1]
    
        return matrix[-1][-1]

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

def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        m=len(grid)
        n=len(grid[0])
        matrix=[[0 for _ in range(n)] for _ in range(m)]
        
        for i in range(m):
            for j in range(n):
                if i==0 and j==0:
                    matrix[0][0]=grid[0][0]
                elif j==0:
                    matrix[i][j]=matrix[i-1][j]+grid[i][j]
                elif i==0:
                    matrix[i][j]=matrix[i][j-1]+grid[i][j]
                else:
                    matrix[i][j]=min(matrix[i-1][j]+grid[i][j],matrix[i][j-1]+grid[i][j])
        return matrix[-1][-1]

85. 最大矩形
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

示例:

输入:
[
  ["1","0","1","0","0"],
  ["1","0","1","1","1"],
  ["1","1","1","1","1"],
  ["1","0","0","1","0"]
]
输出: 6

代码如下:

class Solution(object):
    def maximalRectangle(self, matrix):
        """
        :type matrix: List[List[str]]
        :rtype: int
        """
        if not matrix:
            return 0
        
        r, c = len(matrix), len(matrix[0])
        height, res = [0]*(c+1), 0
        for row in matrix:
            for i in range(c):
                height[i] = height[i] + 1 if row[i] == '1' else 0
            
            stack = [-1]
            for idx, val in enumerate(height): # height 长度+1,最后一个元素值为0
                while val < height[stack[-1]]:
                    h = height[stack.pop()]
                    res = max(res, h*(idx - stack[-1] - 1))
                    
                stack.append(idx)
                
        return res

  1. 不同的二叉搜索树 II

给定一个整数 n,生成所有由 1 … n 为节点所组成的二叉搜索树。

示例:

输入: 3
输出:
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

代码如下:

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def generateTrees(self, n):
        """
        :type n: int
        :rtype: List[TreeNode]
        """
        if n == 0:
            return []
        return self.dfs(1,n)
       
    def dfs(self, b, e):#b,e为开始和结束数字
        if b > e:
            return [None]
        res = [] # 存放所有二叉搜索树的根节点
        # 遍历每个子树的根节点
        for rootVal in range(b, e + 1):  
        	# 以当前节点为根节点,构造所有可能的子树,
            leftTree = self.dfs(b, rootVal - 1) # 返回可能子树的所有根节点
            rightTree = self.dfs(rootVal + 1, e)
            for i in leftTree:
                for j in rightTree:
                    root = TreeNode(rootVal)
                    root.left = i
                    root.right = j
                    res.append(root) # 存放子树根节点
        return res
  1. 不同的二叉搜索树

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

暴力递归

class Solution:
    def numTrees(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 0 or n == 1:
            return 1
        res = 0
        
        for j in range(1, n + 1):
            res += self.numTrees(j-1) * self.numTrees(n-j)
        
        return res

解题代码:

class Solution(object):
    def numTrees(self, n):
        """
        :type n: int
        :rtype: int
        """
        mem = [0]*(n+1)
        mem[0], mem[1] = 1, 1
        
        for i in range(2, n + 1):   # 表示以1~i为区间构建二叉树的个数
            for j in range(1, i + 1): # 表示在1~i区间上以整数j为根结点可以构造出的二叉搜索树的个数
                mem[i] += mem[j - 1]*mem[i - j] # m[j-1] 表示j-1个整数可以构造左子二叉树的个数, m[i-j]表示i-j个整数可以构建右二叉搜索树的个数
                
        return mem[-1]

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

例如,给定三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

代码如下:

class Solution(object):
    def minimumTotal(self, triangle):
        """
        :type triangle: List[List[int]]
        :rtype: int
        """
        for i in range(len(triangle) - 1, 0, -1):
            for j in range(i):
                triangle[i - 1][j] += min(triangle[i][j], triangle[i][j + 1])
        return triangle[0][0]
  1. 乘积最大子序列

给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。

示例 1:

输入: [2,3,-2,4]
输出: 6

解释: 子数组 [2,3] 有最大乘积 6。
示例 2:

输入: [-2,0,-1]
输出: 0
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        res =[]
        
        def process(nums,i):
            if i==0:
                return 1
            ans = 1
            for j in range(i):
                if nums[i]> nums[j]:
                    ans = max(ans, process(nums,j)+1)
                # 如果nums[i]> nums[j]升序就加1, 当j走到i时也就统计出了0~i上以i结尾的最长递增子序列
            return ans
        for i in range(len(nums)):
            cnt = process(nums,i)
            res.append(cnt)
        print(res)
        return max(res)

解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

class Solution(object):
    def maxProduct(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        B = nums[::-1]
        for i in range(1, len(nums)):
            nums[i] *= nums[i - 1] or 1
            B[i] *= B[i - 1] or 1
        return max(max(nums),max(B)) 
  1. 最大正方形

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

示例:

输入: 

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

输出: 4

代码如下:

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        #用 dp(i,j) 表示以 (i,j) 为右下角,且只包含 1 的正方形的边长最大值。如果我们能计算出所有 dp(i,j) 的值,那么其中的最大值即为矩阵中只包含 1 的正方形的边长最大值,其平方即为最大正方形的面积。
        if len(matrix) == 0 or len(matrix[0]) == 0:
            return 0
        
        maxSide = 0
        rows, columns = len(matrix), len(matrix[0])
        dp = [[0] * columns for _ in range(rows)]
        for i in range(rows):
            for j in range(columns):
                if matrix[i][j] == '1':
                    if i == 0 or j == 0:
                        dp[i][j] = 1
                    else:
                        dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
                    maxSide = max(maxSide, dp[i][j])
        
        maxSquare = maxSide * maxSide
        return maxSquare


  1. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。
n log n 二分, 维持一个最长上升序列
示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

dp[i] 的值代表nums以nums[i]结尾的最长上升子序列的长度

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

二分查找
代码:

class Solution:                                                    
    def lengthOfLIS(self, nums: List[int]) -> int:                 
        def findIdxToReplace(dp, i):  # 找第一个比i大的,经典二分套路            
            l, r = 0, len(dp) - 1                                  
            while l < r:                                           
                mid = (l + r) // 2                                 
                if dp[mid] == i:                                   
                    return mid                                     
                elif dp[mid] < i:                                  
                    l = mid + 1                                    
                else:                                              
                    r = mid                                        
            return l                                               
                                                                   
        dp = [nums[0]]  # 第一个数先放进去, 第二个数开始                         
        for i in range(1, len(nums) - 1):                          
            if nums[i] > dp[-1]:                                   
                dp.append(nums[i])                                 
            else:                                                  
                idx = findIdxToReplace(dp, nums[i])                
                dp[idx] = nums[i]                                  
        # 最后一个数只要看能不能加在最后, 替换掉前面的数也增加不了长度了                         
        return len(dp) if nums[-1] <= dp[-1] else len(dp) + 1      
  1. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3
输出: -1

暴力递归

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:

        def func(rest):
            if rest ==0:
                return 0
            ans = 9999999999
            for coin in coins: 
                if coin <= rest:
                    ans = min(ans, func(rest -coin)+1)

            return ans 

        return  -1  if func(amount) == 9999999999 else func(amount)

代码:

class Solution(object):
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        dp = [float("inf")] * (amount+1)
        dp[0] = 0
        for i in range(1,amount+1):

            for coin in coins:
                if i - coin>= 0:
                    dp[i] = min(dp[i],dp[i-coin]+1)
        
        return dp[-1] if dp[-1]!= float("inf") else -1

213. 打家劫舍 II

代码:

class Solution(object):
    def rob(self, nums):
        n = len(nums)
        if n == 0:
          return 0
        if n <= 2:
          return max(nums)
        # 不抢第一个
        dp1 = [0] * n
        dp1[0] = 0
        dp1[1] = nums[1]
        for i in range(2, n):
          dp1[i] = max(dp1[i-1],nums[i] + dp1[i-2])

        # 不抢最后一个
        dp2 = [0] * n
        dp2[0] = nums[0]
        dp2[1] = max(nums[0],nums[1])
        for i in range(2, n-1):
          dp2[i] = max(dp2[i-1],nums[i] + dp2[i-2])
        return max(dp1[n-1],dp2[n-2])

0/1背包问题

416. 分割等和子集

暴力递归

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        sm= sum(nums)
        if sm % 2 != 0:
            return False
        target = sm /2
        def dfs (i, rest): 
            if i> len(nums)-1:
                return False
            if rest ==0:
                return True
            for j in range(i,len(nums)-1):
                if nums[j] <=rest:
                   return dfs(j+1,rest -nums[j]) or dfs(j+1,rest )
            return False

        return dfs(0, target)

动态规划:

class Solution(object):
    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        c=sum(nums)
        if c% 2 !=0:
            return False
        c=c//2
        w=[False]*(c+1)
        w[0]=True
        for num in nums:
            for i in range(c,num-1,-1):
            	# num[i] 有两种操作,选择加这个数和不加这个数
                w[i]=w[i] or w[i-num]  
        return w[c]

1049. 最后一块石头的重量 II

class Solution(object):
    def lastStoneWeightII(self, stones):
        """
        :type stones: List[int]
        :rtype: int
        """
        c=sum(stones)
        c=c//2
        dp=[0]*(c+1)
        for stone in stones:
            # 对于前stone个石头,对应大小为j的包,对应能装下的最大质量
            for j in range(c,stone-1,-1):
                # 对于石头stone,大小为j的背包容量可以选择加和不加
                dp[j]=max(dp[j],dp[j-stone]+stone)
                
        return sum(stones) -2*dp[-1]

279. 完全平方数

  • 状态定义:dp[i]表示n的完全平方数的最少数量
  • 状态转移:遍历1…i内的平方数j*j,j由1开始递增
dp[i] = min(dp[i],dp[i-j*j]+1)
  • 边界情况:dp[i]=i
class Solution:
    def numSquares(self, n: int) -> int:
        dp = list(range(n+1))
        for i in range(1,n+1):
            j = 1
            while i - j*j>=0:
                dp[i] = min(dp[i],dp[i-j*j]+1)
                j += 1
        return dp[-1]

1749 任意子数组和的绝对值的最大值

正数求最大,负数求最小

class Solution:
    def maxAbsoluteSum(self, nums: List[int]) -> int:
   

        # ans = f_max = f_min = 0
        # for x in nums:
        #     f_max = max(f_max, 0) + x
        #     f_min = min(f_min, 0) + x
        #     ans = max(ans, f_max, -f_min)
        # return ans

        # sum_list = [0]
        # tmp =0
        # for i in range(len(nums)): 
        #     tmp +=nums[i]
        #     sum_list.append(tmp)
        #     print(tmp)
        # return max(sum_list) - min(sum_list)
        res= 0
        dp_max = [0] * (len(nums)+1)
        dp_min = [0] * (len(nums)+1)
        for i in range(1,len(nums) +1):
            dp_max[i] = max(dp_max[i-1] + nums[i-1],nums[i-1])
            dp_min[i] = min(dp_min[i-1] + nums[i-1],nums[i-1])
            res = max(res,abs(dp_max[i]),abs(dp_min[i]))
        return res 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值