题解分享|背包总结|带题解
423
2024.01.22
2024.01.22
发布于 未知归属地

专项突破——背包问题

引用:

详细细节:咱就把01背包问题讲个通透! - 力扣(LeetCode)动态规划:关于完全背包,你该了解这些! - 力扣(LeetCode)

其他总结:【背包问题】 - 力扣(LeetCode)基础背包问题总结 - 力扣(LeetCode)

正文:

二维:

dp[i][j]的含义: 从下标为0,i-1的物品里任意取,放进容量为j的背包,最大价值。

一维:

dp[i],目前,背包重量为i时,最大的价值。

一些模板请参考引用。

总结:

二维一维
遍历顺序正逆序遍历顺序正逆序
0-1背包都行都正先物品,后目标目标逆序
完全背包组合数:都行 排列数:先目标,后物品, 且dp状态转移方程会变 (推荐用一维)都正组合数:先物品,后目标 排列数:先目标,后物品都正

注意点:

二维:因为dp状态转移方程不同(大概率是if-else), 所以写循环的时候要判断一下。

一维:因为dp状态转移方程不同(大概率是if),所以写循环的时候不要判断,直接写在内循环的for结构里。

例题:

0-1背包问题:

1049. 最后一块石头的重量 II - 力扣(Leetcode)

解答:

二维解法:

class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        """
        题目相当于:分成两个阵营,相互粉碎,求最小值
        变成0-1背包问题:从中拿石头,求能重量和最接近sum / 2的结果
        """

        Sum = sum(stones)
        dp = [[False] * (Sum + 1) for _ in range(len(stones) + 1)]
        for i in range(len(stones) + 1):
            # True表示能取到
            dp[i][0] = True

        # 二维数组,遍历顺序无所谓
        
        for j in range(0, Sum + 1):
            for i in range(1, len(stones) + 1):
                if j - stones[i-1] >= 0:
                    dp[i][j] = dp[i-1][j - stones[i-1]] or dp[i-1][j]
                else:
                    dp[i][j] = dp[i-1][j]

        res = Sum
        for i in range(Sum + 1):
            if dp[-1][i]:
                res = min(res, abs(Sum-2*i))
        return res

一维解法:

class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        """
        题目相当于:分成两个阵营,相互粉碎,求最小值
        变成0-1背包问题:从中拿石头,求能重量和最接近sum / 2的结果
        """

        Sum = sum(stones)
        dp = [False] * (Sum+1)
        dp[0] = True
        
        # 一维,先物品后背包,背包逆序
        for i in range(1, len(stones) + 1):
            for j in range(Sum, stones[i-1]-1, -1):
                if dp[j-stones[i-1]] != 0:
                    dp[j] = True

        res = Sum
        for i in range(len(dp)):
            if dp[i]:
                res = min(res, abs(Sum-2*i))

        return res

**416. 分割等和子集 - 力扣(Leetcode)

**

解答:

二维解法

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        # 0-1 背包问题

        # 先判断  
        nums_sum = sum(nums)
        if nums_sum % 2 == 1:
            return False


        n = len(nums)
        target = nums_sum // 2

        # dp[i][j]表示前i - 1个数能否取到j - 1
        dp = [[False] * (target + 1) for _ in range(len(nums) + 1)]
        for i in range(len(nums) + 1):
            dp[i][0] = True

        # 二维0-1背包物品目标遍历顺序随便
        for i in range(1, len(nums) + 1):
            for j in range(1, target + 1):
                if j >= nums[i-1]:
                    dp[i][j] = dp[i-1][j] or dp[i-1][j - nums[i-1]]
                else:
                    dp[i][j] = dp[i-1][j]


        return dp[-1][-1]

一维解法:
class Solution:    def canPartition(self, nums: List[int]) -> bool:        # 0-1 背包问题         # 先判断          nums_sum = sum(nums)        if nums_sum % 2 == 1:            return False         n = len(nums)        target = nums_sum // 2         # dp[i]表示能否取到i - 1        dp = [False] * (target + 1)        dp[0] = True         # 一维0-1背包,先物品,后目标,目标逆序        for num in nums:            for j in range(target, num - 1, -1):                    dp[j] = dp[j] or dp[j - num]         return dp[-1] 

完全背包问题:

518. 零钱兑换 II - 力扣(Leetcode)

解答:

二维数组解法:

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        # 完全背包问题,组合数
        dp = [[0] * (amount + 1) for _ in range(len(coins) + 1)]
        
        # 初始化dp,价值为0时,不取硬币,也是一种方案
        for i in range(len(coins) + 1):
            dp[i][0] = 1

        #这两个循环可以颠倒位置
        for i, coin in enumerate(coins):
            for j in range(amount+1):
                
                # 此处对应注意点
                if j - coin >= 0:
                    dp[i+1][j] = dp[i+1][j-coin] + dp[i][j]
                else:
                    dp[i+1][j] = dp[i][j]

        return dp[-1][-1]

一维数组解法:

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        
        # 完全背包问题,组合数
        dp = [0] * (amount + 1)
        dp[0] = 1

        # 先物品后背包,顺序不能颠倒
        for coin in coins:
            for i in range(coin, amount+1):            
            # 第10行相当于12,13行,把if写在for循环中
            # for i in range(amount+1):
            #     if i-coin >= 0:

                    dp[i] += dp[i-coin]


        return dp[-1]

377. 组合总和 Ⅳ - 力扣(Leetcode)

二维解法:

(推荐用一维做)

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        # 完全背包问题, 排列数
        
        # dp[i][j]表示下标用0到i-1,能取到j-1的方案数
        dp = [[0] * (target + 1) for _ in range(len(nums) + 1)]
        
        for i in range(len(nums) + 1):
            dp[i][0] = 1
            
        # 先目标后物品
        for j in range(1, target + 1):
            for i in range(1, len(nums) + 1):

                if j - nums[i-1] >= 0:
                    dp[i][j] = dp[-1][j-nums[i-1]] + dp[i-1][j]
                
                else:
                    dp[i][j] = dp[i-1][j]


        return dp[-1][-1]

一维解法

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        # 完全背包问题, 排列数
        # dp[i]表示能取到i-1的方案数
        dp = [0] * (target + 1)
        dp[0] = 1
        
        # 因为求排列数,所以先背包,后物品
        for i in range(1, target + 1):
            for num in nums:
                if i - num >= 0:
                    dp[i] += dp[i-num]

        return dp[-1]
评论 (0)