leetcode-动态规划【背包问题】

背包问题篇:

基础背包:

416. 分割等和子集

1049. 最后一块石头的重量ii

494. 目标和

474. 一和零

完全背包:

518. 零钱兑换ii

377. 组合总和iv

70. 爬楼梯

322. 零钱兑换

279. 完全平方数

139. 单词拆分

多重背包:


0-1背包:(所有元素只能放入一次)

n件物品和最大承受重量为w的背包,其中第i件物品的重量是weight[i],得到的价值是value[i],每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

1. 确定dp数组以及下标的含义

dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。i:物品编号,j:背包容量,dp[i][j]:价值总和

2. 确定递推公式

  • 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以被背包内的价值依然和前面相同。)
  • 放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

3. dp数组初始化

dp[i][0]:背包容量为0因此价值总和为0

dp[0][j]:存放物品0的时候背包价值总和,当j<weight[0]价值总和为0,其余情况价值总和为value[0]

4. 确定遍历顺序:先遍历物品/背包重量

5. 举例推导dp数组

416. 分割等和子集

class Solution(object):
    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        if sum(nums) % 2: return False
        # 要求找到元素和为sum/2的子集
        target = sum(nums) // 2
        # dp[i][j]代表可装物品为0-i,背包容量为j的情况下,背包内容量的最大价值(最大子集和)
        dp = [[0 for _ in range(target+1)] for _ in range(len(nums))]
        # 背包容量为0且只能放入nums[i]的情况下背包最大价值(最大子集和)
        for j in range(nums[0],target+1):
            dp[0][j] = nums[0]
        # 递推公式(每行从左到右遍历)
        for i in range(1, len(nums)):
            for j in range(1, target+1):
                # 当背包不能容纳nums[i]时
                if j < nums[i]:
                    dp[i][j] = dp[i-1][j]
                # 当背包可以容纳nums[i]时
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-nums[i]] + nums[i])
        return dp[-1][-1] == target

使用滑动数组:

class Solution(object):
    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        if not nums or sum(nums) % 2: return False
        target = sum(nums) // 2
        # 滑动数组(因为只需要保留上一行的值因此可以使用一维数组)
        dp = [0 for _ in range(target+1)]
        for i in range(len(nums)):
            for j in range(target, nums[i]-1, -1):
                dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])
        return dp[-1] == target

1049. 最后一块石头的重量ii

class Solution(object):
    def lastStoneWeightII(self, stones):
        """
        :type stones: List[int]
        :rtype: int
        """
        # 将stones分为重量差不多的两堆
        target = sum(stones) // 2
        # dp设为滑动数组,dp[j]存储最接近重量j其不超过的石头总重量
        dp = [0 for _ in range(target+1)]
        # 遍历stones
        for i in range(len(stones)):
            for j in range(target, stones[i]-1, -1):
                dp[j] = max(dp[j], dp[j-stones[i]]+stones[i])
        return sum(stones)-2*dp[-1]

494. 目标和

class Solution(object):
    def findTargetSumWays(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        if len(nums) == 1 and nums[0] != target and nums[0] != -1 * target:
            return 0
        sum_nums = sum(nums)
        if target > sum_nums or target < -1 * sum_nums: return 0
        # dp[][sum_nums]为中心点
        dp = [[0 for _ in range(2*sum_nums+1)] for _ in range(len(nums))]
        for j in range(2*sum_nums+1):
            if abs(j - sum_nums) == nums[0]:
                if nums[0] == 0: dp[0][j] = 2
                else: dp[0][j] = 1
        for i in range(len(nums)-1):
            for j in range(2*sum_nums+1):
                if dp[i][j] == 0: continue
                dp[i + 1][j - nums[i + 1]] += dp[i][j]
                dp[i + 1][j + nums[i + 1]] += dp[i][j]
        return dp[-1][target+sum_nums]

第二种方法:

假设nums = [3,1,2,5,4],此时选3和1前面的符号为加号,其余为减号,那么此时加法获得的和为3+1=4,减法获得的和为2+5+4=11,也可以通过sum(nums)-3-1=15-3-1=11获得。以此类推,设加法获得的和为x,则减法获得的和为(sum-x),问题转化满足x-(sum-x)=target / x=(sum+target)/2有几种方案。

class Solution(object):
    def findTargetSumWays(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        sum_nums = sum(nums)
        # 注意边界条件
        if abs(target) > sum_nums or (sum_nums + target) % 2 == 1: return 0
        bagSize = (sum_nums + target) // 2
        dp = [0] * (bagSize + 1)
        dp[0] = 1
        for i in range(len(nums)):
            for j in range(bagSize, nums[i] - 1, -1):
                dp[j] += dp[j - nums[i]]
        return dp[bagSize]

474. 一和零

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]

class Solution(object):
    def findMaxForm(self, strs, m, n):
        """
        :type strs: List[str]
        :type m: int
        :type n: int
        :rtype: int
        """
        # dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]
        dp = [[0 for _ in range(n+1)] for _ in range(m+1)]
        for str in strs:
            onenum = str.count('1')
            zeronum = str.count('0')
            for i in range(m, zeronum-1, -1):
                for j in range(n, onenum-1, -1):
                    dp[i][j] = max(dp[i][j], dp[i-zeronum][j-onenum] + 1)
        return dp[m][n]

完全背包:(元素可以重复放入)

518. 零钱兑换ii

class Solution(object):
    def change(self, amount, coins):
        """
        :type amount: int
        :type coins: List[int]
        :rtype: int
        """
        # dp[i][j]表示coins[i]的面值加入排列后,总和为amount的组合数
        dp = [0 for _ in range(amount+1)]
        dp[0] = 1
        for i in range(len(coins)):
            for j in range(coins[i], amount+1):
                dp[j] += dp[j-coins[i]]
        return dp[-1]

377. 组合总和iv

class Solution(object):
    def combinationSum4(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        dp = [0 for _ in range(target+1)]
        dp[0] = 1
        for j in range(target+1):
            for i in range(len(nums)):
                if j-nums[i] >= 0:
                    dp[j] += dp[j-nums[i]]
        return dp[-1]

70. 爬楼梯

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        dp = [0 for _ in range(n+1)]
        dp[0] = 1
        for j in range(n+1):
            for step in range(1,3):
                if j - step >= 0:
                    dp[j] += dp[j-step]
        return dp[-1]

322. 零钱兑换

class Solution(object):
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        dp = [amount + 1] * (amount + 1)
        dp[0] = 0
        for coin in coins:
            for j in range(coin, amount + 1):
                dp[j] = min(dp[j], dp[j - coin] + 1)
        if dp[amount] < amount + 1:
            return dp[amount]
        else:
            return -1

279. 完全平方数

class Solution(object):
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        square, i = [], 1
        while i**2 <= n:
            square.append(i**2)
            i += 1

        dp = [n+1] * (n+1)
        dp[0] = 0
        for i in range(len(square)):
            for j in range(square[i], n+1):
                dp[j] = min(dp[j], dp[j-square[i]]+1)
        if dp[n] > n+1:
            return -1
        else:
            return dp[n]

139. 单词拆分

class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        # dp[j]=1即截至此处s[:j+1]可划分,dp[j]=0即不可划分
        dp = [0 for _ in range(len(s)+1)]
        dp[0] = 1
        for j in range(1, len(s)+1):
            for word in wordDict:
                if j >= len(word):
                    dp[j] = dp[j] or (dp[j - len(word)] and word == s[j - len(word):j])
        return dp[-1] == 1

多重背包:

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法LeetCode动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法数据结构基础》等,来深入理解这种算法思想。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值