背包问题总结:你的背包背到现在还没烂

背包问题

2020年9月5日 周六 晴 苏州工业园区

0-1背包

416. 分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total % 2 == 1:
            return False
        V = total // 2
        N = len(nums)
        F = (V+1) * [-101*200] 
        F[0] = 0
        for i in range(N):
            for v in range(V, nums[i]-1, -1):
                #print(v, v-nums[i], v-1)
                F[v] = max(F[v], F[v-nums[i]]+1)
        print(F)
        return True if F[V]>0 else False
494. 目标和
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。
对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
提示:
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。

P − N = s u m ( a i ) P-N = sum(a_i) PN=sum(ai), P + N = a m o u n t P+N = amount P+N=amount, P = s u m ( a i ) + a m o u n t 2 P = \frac{sum(a_i)+amount}{2} P=2sum(ai)+amount

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        """
        if len(nums) == 0:
            if S == 0:
                return 1
            else:
                return 0
        return (self.findTargetSumWays(nums[:-1], 
        S-nums[-1]) + self.findTargetSumWays(nums[:-1], S+nums[-1]))
        
        N = len(nums)
        F = 
        for i in range(N):
        """
        """
        N = len(nums)
        V = sum(nums)
        if  S>V or S<-V:
            return 0
        F = (4001)*[0]
        F[1000+nums[0]] += 1
        F[1000-nums[0]] += 1
        for i in range(1, N):
            tmp = len(F) * [0]
            for j in range(2001):
                if F[j]:
                    for k in [-1, 1]:
                        tmp[j+k*nums[i]] += F[j] 
            F = tmp[:]
        return F[S+1000]
        """
        # P - N = sum(a_i), P+N = amount, P = sum(a_i) 
        V = sum(nums) + S
        if sum(nums)<S or V%2:
            return 0
        else:
            V = V // 2
        dp = [0 for i in range(V+1)]
        dp[0] = 1
        for num in nums:
            for v in range(V, num-1, -1):
                dp[v] += dp[v-num]
        return dp[V]
92-背包问题(Lintcode)
在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]

样例 1:
	输入:  [3,4,8,5], backpack size=10
	输出:  9
样例 2:
	输入:  [2,3,5,7], backpack size=12
	输出:  12
class Solution:
    """
    @param m: An integer m denotes the size of a backpack
    @param A: Given n items with size A[i]
    @return: The maximum size
    """
    def backPack(self, m, A):
        # write your code here
        ### 0-1背包
        F = (m+1) * [False]
        F[0] = True
        ret = 0
        for a in A:
            for v in range(m, a-1, -1):
                if F[v-a]:
                    F[v] = F[v-a]
                    ret = max(ret, v)
                
        return ret
        """
        完全背包
        dp = (m+1) * [False]
        dp[0] = True
        ret = 0
        for v in range(1, m+1):
            for a in A:
                if v-a>=0 and dp[v-a]:
                    dp[v] = True
                    ret = max(ret, v)
        print(dp)
        return ret
        """
125.背包问题 II(Lintcode)
有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.

问最多能装入背包的总价值是多大?

样例
样例 1:

输入: m = 10, A = [2, 3, 5, 7], V = [1, 5, 2, 4]
输出: 9
解释: 装入 A[1] 和 A[3] 可以得到最大价值, V[1] + V[3] = 9 
样例 2:

输入: m = 10, A = [2, 3, 8], V = [2, 5, 8]
输出: 10
解释: 装入 A[0] 和 A[2] 可以得到最大价值, V[0] + V[2] = 10
class Solution:
    """
    @param m: An integer m denotes the size of a backpack
    @param A: Given n items with size A[i]
    @param V: Given n items with value V[i]
    @return: The maximum value
    """
    def backPackII(self, m, A, V):
        # write your code here
        F = (m+1)*[0]
        for i in range(len(A)):
            for v in range(m, A[i]-1, -1):
                F[v] = max(F[v], F[v-A[i]]+V[i])
        return F[m]

完全背包

322. 零钱兑换
给定不同面额的硬币 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:
        N = len(coins)
        INF = 1000000000
        dp = (amount+1)*[INF]
        dp[0] = 0
        for i in range(N):
            for v in range(coins[i], amount+1):
                dp[v] = min(dp[v], dp[v-coins[i]]+1)
        return dp[amount] if dp[amount] != INF else -1
518. 零钱兑换 II
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。
假设每一种面额的硬币有无限个。 
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:
输入: amount = 10, coins = [10] 
输出: 1
注意:
你可以假设:
0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数
class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp = (amount+1)*[0]
        dp[0] = 1
        N = len(coins)
        for coin in coins:
            for v in range(coin, amount+1):
                dp[v] += dp[v-coin]
        return dp[amount]
279. 完全平方数
279. 完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。
你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
class Solution:
    def numSquares(self, n: int) -> int:
        C = []
        INF = 100000000
        dp = (n+1) * [INF]
        dp[0] = 0
        for i in range(1, n+1):
            tmp = i*i
            if tmp<=n:
                C.append(tmp)
            else:
                break
        for c in C:
            for v in range(c, n+1):
                dp[v] = min(dp[v], dp[v-c]+1)
        return dp[n]

二维背包

474. 一和零
在计算机界中,我们总是追求用有限的资源获取最大的收益。
现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含0和1字符串的数组。
你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。
每个0和1至多被使用一次。
注意:
给定 0 和 1 的数量都不会超过 100。
给定字符串数组的长度不会超过 600。
示例 1:
输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
输出: 4
解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。
示例 2:
输入: Array = {"10", "0", "1"}, m = 1, n = 1
输出: 2
解释: 你可以拼出 "10",但之后就没有剩余数字了。更好的选择是拼出 "0" 和 "1" 。
class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        N = len(strs)
        C = [[0, 0] for i in range(N)]
        F = [(n+1)*[0] for i in range(m+1)] #(m+1) * [(n+1)*[0]]

        for i in range(N):
            for s in strs[i]:
                if s == '0':
                    C[i][0] += 1
                else:
                    C[i][1] += 1
        for i in range(N):
            for j in range(m, C[i][0]-1, -1):
                for k in range(n, C[i][1]-1, -1):
                    F[j][k] = max(F[j][k], F[j-C[i][0]][k-C[i][1]]+1)
        #print(C)
        return F[m][n]

分组背包

背包相关的问题

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。
进阶:
如果给定的数组中含有负数会怎么样?
问题会产生什么变化?
我们需要在题目中添加什么限制来允许负数的出现?
class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        nums.sort()
        dp = (target+1)*[0]
        dp[0] = 1
        for v in range(target+1):
            for num in nums:
                if v-num>=0:
                    dp[v] += dp[v-num]
                else:
                    break
        return dp[target]
139. 单词拆分
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,
判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
     注意你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        n = len(s)
        dp = (n + 1) * [False]
        dp[0] = True
        for i in range(1, n+1):
            for j in range(i):
                if s[j:i] in wordDict and dp[j]:
                    dp[i] = True
                    break
        return dp[n]

参考

  1. 背包问题九讲 2.0 beta1.2 崔添翼 (Tianyi Cui): https://github.com/tianyicui/pack
  2. 力扣: https://leetcode-cn.com
  3. 领扣: https://www.lintcode.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值