343. 整数拆分*
给定一个正整数 n
,将其拆分为 k
个 正整数 的和( k >= 2
),并使这些整数的乘积最大化。
返回 你可以获得的最大乘积 。
最总要的就是想明白递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j})
随想录解法:
class Solution:
# 假设对正整数 i 拆分出的第一个正整数是 j(1 <= j < i),则有以下两种方案:
# 1) 将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j * (i-j)
# 2) 将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j * dp[i-j]
def integerBreak(self, n):
dp = [0] * (n + 1) # 创建一个大小为n+1的数组来存储计算结果
dp[2] = 1 # 初始化dp[2]为1,因为当n=2时,只有一个切割方式1+1=2,乘积为1
# 从3开始计算,直到n
for i in range(3, n + 1):
# 遍历所有可能的切割点
for j in range(1, i // 2 + 1):
# 计算切割点j和剩余部分(i-j)的乘积,并与之前的结果进行比较取较大值
dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j)
return dp[n] # 返回最终的计算结果
96.不同的二叉搜索树*
给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
难点也是递推公式:dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量
class Solution:
def numTrees(self, n: int) -> int:
dp = [0] * (n + 1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
for j in range(i):
dp[i] += dp[i-j-1] * dp[j]
return dp[n]
随想录解法:
class Solution:
def numTrees(self, n: int) -> int:
dp = [0] * (n + 1) # 创建一个长度为n+1的数组,初始化为0
dp[0] = 1 # 当n为0时,只有一种情况,即空树,所以dp[0] = 1
for i in range(1, n + 1): # 遍历从1到n的每个数字
for j in range(1, i + 1): # 对于每个数字i,计算以i为根节点的二叉搜索树的数量
dp[i] += dp[j - 1] * dp[i - j] # 利用动态规划的思想,累加左子树和右子树的组合数量
return dp[n] # 返回以1到n为节点的二叉搜索树的总数量
背包问题
01 背包
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
dp[i][j]表示的是从0-i物品中任取放入承重为j的背包里面,每个物品最多取一件,最多能装多大价值的物品。
02 背包
def test_2_wei_bag_problem1():
# 2 2 3 1 5 2
# 2 3 1 5 4 3
weight = [2, 2, 3, 1, 5, 2]
value = [2, 3, 1, 5, 4, 3]
bagweight = 3
# 二维数组
dp = [[0] * (bagweight + 1) for _ in range(len(weight))]
# 初始化 因为dp[i][j]要用前一行的值,所以第0行需要我们进行初始化,第0行就是最多可以取一件物品0
for j in range(weight[0], bagweight + 1):
dp[0][j] = value[0]
# weight数组的大小就是物品个数
for i in range(1, len(weight)): # 遍历物品
for j in range(bagweight + 1):
if j < weight[i]:
# 物品i放不下背包时
dp[i][j] = dp[i - 1][j]
else:
# 物品i能放得进背包时,看是不放i更大还是放i更大
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
print(dp)
# 一维数组
dp = [0] * (bagweight + 1)
for i in range(len(weight)):
for j in range(bagweight, weight[i]-1, -1):
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
print(dp)
416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
解法:化为01背包问题
此时bagweight = sum(nums) // 2
物品i的重量和价值都是nums[i],其实这里不需要考虑物品价值,
dp[i][j]表示的是从0-i物品中任取,每个物品最多取一件,能否达到质量j
dp[j] = dp[j] or dp[j - num] 表示两种情况:
不放质量为num的物品,看j是否可以取到
放质量为num的物品,看j-num是否可以取到
只要上述两种情况任意为true,此时dp[j]就为true.
class Solution:
def canPartition(self, nums: List[int]) -> bool:
# [1,5,11,5] total_sum = 11
# dp = [False] * 12
# nums: 1 for i in range(11, 0, -1): dp[i] = dp[i] or dp[i-1] dp[1] = True
# nums: 5 for i in range(11, 4, -1): dp[6] = dp[1] = True, dp[5] = dp[0] = True
# nums: 11 for i in range(11, 10, -1): dp[11] = True
# nums: 5 for i in range(11, 4, -1): dp[10] = dp[5] = True
total_sum = sum(nums)
if total_sum % 2 != 0:
return False
target_sum = total_sum // 2
dp = [False] * (target_sum + 1)
dp[0] = True
for num in nums:
# 从target_sum逆序迭代到num,步长为-1
for j in range(target_sum, num - 1, -1):
dp[j] = dp[j] or dp[j - num]
return dp[target_sum]
1049.最后一块石头的重量II
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎; - 如果
x != y
,那么重量为x
的石头将会完全粉碎,而重量为y
的石头新重量为y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0
。
转化为背包问题:背包质量为sum(stones) // 2 向下取整,看最大可以放进多大质量的石头
则最后返回的石头重量为(sum(stones) - dp[-1][-1]) - dp[-1][-1]
dp[i][j]表示背包承重为j的情况下,在0-i块石头中任取,最多能放进多重的石头
二维动态数组
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
bagweight = sum(stones) // 2
dp = [[0] * (bagweight + 1) for _ in range(len(stones))]
for j in range(stones[0], bagweight+1):
dp[0][j] = stones[0]
for i in range(1, len(stones)):
for j in range(bagweight+1):
if j < stones[i]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-stones[i]] + stones[i])
return sum(stones) - 2*dp[-1][-1]
一维动态数组
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
bagweight = sum(stones) // 2
dp = [0] * (bagweight + 1)
for i in range(len(stones)):
for j in range(bagweight, stones[i] - 1, -1):
dp[j] = max(dp[j], dp[j-stones[i]] + stones[i])
return sum(stones) - 2*dp[-1]