数据结构与算法-LeetCode中的背包问题

背包问题

判定

给定一个target(可以是数字或字符串)
给定数组nums(nums的元素可以是数字或字符)
问:能否使用nums中的元素做各种排列组合得到target

常见背包问题分类

1.组合问题
377.组合总和 Ⅳ
494.目标和
518.零钱兑换 II
状态转移

dp[i] += dp[i-num]

2.True、False问题:
139.单词拆分
416.分割等和子集
状态转移

dp[i] = dp[i] or dp[i-num]

3.最大最小问题:
474.一和零
322.零钱兑换
状态转移

dp[i] = min(dp[i],dp[i-num]+1)max(dp[i],dp[i-num]+1)

技巧

1.如果是0-1背包,即数组中的元素不可重复使用,nums放在外循环,target在内循环,且内循环倒序;

for num in nums:
    for i in range(target, num-1, -1):

2.如果是完全背包,即数组中的元素可重复使用,nums放在外循环,target在内循环。且内循环正序。

for num in nums:
    for i in range(num, target+1):

完全背包内外循环可以调换

3.如果组合问题需考虑元素之间的顺序,需将target放在外循环,将nums放在内循环。

for i in range(1, target+1):
    for num in nums:

参考资料:
https://leetcode-cn.com/problems/combination-sum-iv/solution/xi-wang-yong-yi-chong-gui-lu-gao-ding-bei-bao-wen-/

LeetCode

1.组合问题

377. 组合总和 Ⅳ
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
示例:

nums = [1, 2, 3]
target = 4

所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

494. 目标和
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

方法一:
求方法数:dp数组记录方法数
定义状态:dp[i][j]表示nums中下标为0-i的元素,加减得到j的方法数
状态转移方程:d[i][j] = d[i-1][j-nums[i] + d[i-1][j+nums[i]

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        Sum = sum(nums)
        if S>Sum or S<-Sum:
            return 0
        n = len(nums)
        dp = [[0 for _ in range(Sum*2+1)] for _ in range(n)]

        if nums[0]==0:
            dp[0][Sum] = 2
        else:
            dp[0][Sum-nums[0]] = 1
            dp[0][Sum+nums[0]] = 1
        
        for i in range(1,n):
            for j in range(Sum*2+1):
                if j+nums[i]<Sum*2+1:
                    dp[i][j] += dp[i-1][j+nums[i]]
                if j-nums[i]>=0:
                    dp[i][j] += dp[i-1][j-nums[i]]
        
        return dp[-1][Sum+S]

方法二:
0-1背包问题
原问题等同于:找到nums中的两个子集,使得其差为target
正子集P,负子集N
SP - SN = target
SP + SN + SP + SN = target + SP +SN
2*SP = target + Sum
等同于找,和为(target+Sum)//2的子集

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        Sum = sum(nums)
        if S>Sum or S<-Sum or (Sum+S)%2!=0:
            return 0
        
        n = len(nums)
        SP = (Sum+S)//2

        dp = [0 for _ in range(SP+1)]
        dp[0] = 1

        for num in nums:
            for j in range(SP,num-1,-1):
                dp[j] += dp[j-num]

        return dp[-1]

518. 零钱兑换 II
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
与377的区别,元素顺序不同视为同一组合方式

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp = [0 for _ in range(amount+1)]
        dp[0] = 1
        for coin in coins:
            for i in range(1,amount+1):
                if i>=coin:
                    dp[i] += dp[i-coin]
        return dp[amount]

2.True/False问题

139.单词拆分
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:

        n = len(s)
        dp = [False for _ in range(n+1)]
        dp[0] = True

        for i in range(n):
            for j in range(i+1,n+1):
                if dp[i] and s[i:j] in wordDict:
                    dp[j] = True
        
        return dp[-1]

416.分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200

0-1背包

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        Sum = sum(nums)
        if Sum%2!=0:
            return False
        
        target = Sum//2

        dp = [None for _ in range(target+1)]
        dp[0] = True

        for num in nums:
            for i in range(target,num-1,-1):
                dp[i] = dp[i] or dp[i-num]
        
        return dp[-1]

3.最大最小问题

474.一和零
在计算机界中,我们总是追求用有限的资源获取最大的收益。
现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。
你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。
注意:
给定 0 和 1 的数量都不会超过 100。
给定字符串数组的长度不会超过 600。

m个0,n个1可以看作是背包的容量
数组中的字符串看作是放入背包的物品
dp[i][j][k] 表示在数组[0:i+1]个字符串中,j个0,k个1可以拼出的最大数量

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        if not strs:
            return 0
        
        dics = []
        for s in strs:
            dic = [0,0]
            for c in s:
                if c=='0':
                    dic[0] += 1
                elif c=='1':
                    dic[1] += 1
            dics.append(dic)
        
        l = len(strs)
        f = [[0 for _ in range(n+1)] for _ in range(m+1)]

        for j in range(m+1):
            for k in range(n+1):
                if j>=dics[0][0] and k>=dics[0][1]:
                    f[j][k] = 1
      
        for i in range(1,l):
            for j in range(m,dics[i][0]-1,-1):
                for k in range(n,dics[i][1]-1,-1):
                    f[j][k] = max(f[j][k],f[j-dics[i][0]][k-dics[i][1]]+1)
        
        return f[-1][-1]

322.零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
说明:
你可以认为每种硬币的数量是无限的。

硬币可以重复使用,完全背包

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

        dp = [9999 for _ in range(amount+1)]
        dp[0] = 0

        for coin in coins:
            for i in range(coin,amount+1):
                dp[i] = min(dp[i],dp[i-coin]+1)
        
        if dp[amount]==9999:
            return -1
        return dp[-1]

参考资料:
https://leetcode-cn.com/problems/target-sum/solution/bei-bao-da-qia-ti-20-1bei-bao-qia-hao-zhuang-man-b/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值