1049.最后一块石头的重量Ⅱ
题目链接
讲解链接
思路:与前一题基本一致,将所有石头分为重量最接近sum/2的两堆,碰撞之后剩下的就是最小值
动规五部曲:
1.dp数组及其下标含义:重量为j的背包所能容纳的最大价值(本题中也就是重量)为dp[j];
2.确定递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
3.dp数组初始化:初始化全为0即可;
4.确定遍历顺序:先遍历石头,再遍历背包;
5.打印dp数组:当stones = [2,7,4,1,8,1]时,dp = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]。
class Solution:def lastStoneWeightII(self, stones: List[int]) -> int:target = sum(stones) // 2 # 分割值为总和的一半dp = [0] * (target + 1) # 初始化长度为target + 1的全零dp数组for i in stones: # 先遍历石头for j in range(target, i - 1, -1): # 再倒序遍历背包dp[j] = max(dp[j], dp[j - i] + i)return sum(stones) - 2 * dp[-1] # 返回碰撞后所剩的值
494.目标和
题目链接
讲解链接
本题类似于回溯算法中的组合总和问题,代码如下:
class Solution:def backtracking(self, candidates, target, total, startIndex, path, result):if total == target: # 回溯终止条件result.append(path[:])for i in range(startIndex, len(candidates)):if total + candidates[i] > target: # 大于目标值则直接跳出循环breaktotal += candidates[i]path.append(candidates[i])self.backtracking(candidates, target, total, i + 1, path, result)total -= candidates[i]path.pop()def findTargetSumWays(self, nums: List[int], target: int) -> int:total = sum(nums)if target > total: # 目标和大于总和说明无解return 0if (target + total) % 2 != 0: # 目标和与总和加起来的值必须是偶数才有解,否则无解,例如目标和为3,总和为5,这时和为8,有解,若目标和为4,则无解,因为5个1不可能求出4return 0bagSize = (target + total) // 2 # 将问题转化为组合总和问题,bagSize就是新的目标和result = []nums.sort()self.backtracking(nums, bagSize, 0, 0, [], result)return len(result)
动态规划思路:假设加法的总和(nums中取加号的数)为add,减法的总和(nums中取减号的数)为sub,所以 add + sub = sum,add - sub = target,那么:
sub = sum - add → add - sub = (sum - add) = target → add = (target + sum) / 2
此时问题就转化为,装满容量为 add 的背包,有几种方法。
1.dp数组及其下标含义:装满容量为j的背包有dp[j]种方法;
2.确定递推公式:已知nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。所以递推公式为:
dp[j] += dp[j - nums[i]];
3.dp数组初始化:从递推公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递推结果将都是0。
4.确定遍历顺序:nums放在外循环,target在内循环,且内循环倒序,也就是先物品后背包。
5.打印dp数组
class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int:if abs(target) > sum(nums): # 目标绝对值大于总和说明无解return 0if (target + sum(nums)) % 2 != 0: # 不是偶数说明无解return 0target_sum = (target + sum(nums)) // 2 # 求出目标和(代表背包总容量)dp = [0] * (target_sum + 1)dp[0] = 1 # j等于0时初始化为1for i in nums: # 先物品(数)for j in range(target_sum, i - 1, -1): 后背包(目标和)dp[j] += dp[j - i] # 递推公式return dp[-1]
474.一和零
题目链接
讲解链接
本题中strs数组里的元素就是物品,不同长度的字符串就是不同大小的待装物品。而m和n相当于是一个拥有两个维度的背包。
动规五部曲:
1.dp数组及其下标含义:dp[i][j]指最多有i个0和j个1的strs的最大子集的大小为dp[i][j];
2.确定递推公式:dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。然后我们在遍历的过程中,取dp[i][j]的最大值。所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
3.dp数组初始化:全部初始化为0;
4.确定遍历顺序:先物品(字符串)后背包(两个维度)
5.打印dp数组
class Solution:def findMaxForm(self, strs: List[str], m: int, n: int) -> int:dp = [[0] * (n + 1) for _ in range(m + 1)]for str in strs:zero = str.count('0')one = str.count('1')for i in range(m, zero - 1, -1):for j in range(n, one - 1, -1):dp[i][j] = max(dp[i][j], dp[i - zero][j - one] + 1)return dp[-1][-1]