当前位置: 首页 > news >正文

我的 LeetCode 日记:Day 37 - 解锁动态规划:完全背包问题

在掌握了 0/1 背包之后,今天我将能力升级,开始学习动态规划中的另一大经典模型——完全背包问题

它与 0/1 背包的核心区别只有一个:0/1 背包中的每个物品只有一个,只能选一次;而完全背包中的每个物品有无限个,可以重复选择。这个看似微小的改动,却对状态转移方程和代码实现,尤其是一维空间优化,产生了深刻的影响。

一、理论奠基:完全背包的核心差异

学习完全背包,关键是和 0/1 背包进行对比。

  • 二维 DP: 在 0/1 背包中,状态转移方程是 dp[i][j] = max(dp[i-1][j], values[i-1] + dp[i-1][j - weights[i-1]])。我们总是从 dp[i-1] 这个“上一行”的状态转移而来。但在完全背包中,因为物品 i 可以重复选取,所以当我们决定放入一个物品 i 时,我们是基于“已经放入过物品 i”的状态来决策的。因此,状态转移方程变成了 dp[i][j] = max(dp[i-1][j], values[i-1] + dp[i][j - weights[i-1]])。注意,这里是 dp[i] 而不是 dp[i-1]

  • 一维 DP (空间优化): 这个状态转移的差异,在一维 dp 数组中体现得淋漓尽致。0/1 背包为了使用 i-1 轮的状态,内层循环必须倒序遍历。而完全背包因为要使用第 i 轮的“最新”状态,内层循环必须正序遍历。这一个小小的遍历顺序,就是区分两种背包问题的关键所在。

  • 理论指南:

    • 文章讲解: 代码随想录-完全背包理论基础
    • 视频讲解: B站视频-完全背包
我的实现 1:二维 DP
def solve_complete_knapsack_2d():n, v = map(int, input().split()) # 物品数量, 背包容量weights, values = [], []for _ in range(n):w, val = map(int, input().split())weights.append(w)values.append(val)dp = [[0] * (v + 1) for _ in range(n + 1)]for i in range(1, n + 1):for j in range(1, v + 1):if j < weights[i-1]:dp[i][j] = dp[i-1][j] # 不放物品ielse:# 关键区别:dp[i][...] 而不是 dp[i-1][...]dp[i][j] = max(dp[i-1][j], values[i-1] + dp[i][j - weights[i-1]])print(dp[n][v])
我的实现 2:一维 DP (空间优化)
def solve_complete_knapsack_1d():n, v = map(int, input().split())weights, values = [], []for _ in range(n):w, val = map(int, input().split())weights.append(w)values.append(val)dp = [0] * (v + 1)for i in range(n): # 遍历物品# 关键区别:正序遍历背包容量for j in range(weights[i], v + 1):dp[j] = max(dp[j], values[i] + dp[j - weights[i]])print(dp[v])

二、实战演练:完全背包的应用

1. LeetCode 518. 零钱兑换 II

题目描述: 给定不同面额的硬币和一个总金额。写一个函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

  • 学习感悟: 这是典型的完全背包组合问题。
    • 背包容量: amount
    • 物品: coins 数组中的每种硬币
    • DP 定义: dp[j] 表示凑成金额 j 的组合数。
    • 状态转移: dp[j] += dp[j - coin]。凑成 j 的方法数,等于之前的方法数,加上“凑成 j-coin 的方法数”(因为这些方法都可以通过加上一个 coin 来凑成 j)。
    • 遍历顺序: 因为求的是组合数{1, 2}{2, 1} 算一种),所以必须先遍历物品(coins),再遍历背包(amount。这样可以保证硬币是按固定顺序添加的,避免了重复计数。
  • 资源包:
    • 题目/文章: https://programmercarl.com/0518.%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2II.html
    • 视频讲解: https://www.bilibili.com/video/BV1KM411k75j
我的实现:一维 DP (组合问题)
class Solution:def change(self, amount: int, coins: List[int]) -> int:dp = [0] * (amount + 1)dp[0] = 1 # 凑成金额0有一种方法:什么都不选# 先遍历物品,再遍历背包,求的是组合数for coin in coins:for j in range(coin, amount + 1):dp[j] += dp[j - coin]return dp[amount]
2. LeetCode 377. 组合总和 Ⅳ

题目描述: 给你一个由 不同 整数组成的数组 nums,和一个目标整数 target。请你从 nums 中找出并返回总和为 target 的元素组合的个数。题目数据保证答案符合 32 位整数范围。

  • 学习感悟: 这道题和上一题非常相似,但有一个本质区别:它求的是排列数{1, 2}{2, 1} 算两种)。这个小小的区别,只需要交换内外层循环的顺序即可解决。
    在这里插入图片描述

    • 遍历顺序: 先遍历背包(target),再遍历物品(nums。这样做的效果是,在计算 dp[j] 时,会依次尝试放入 nums 中的每一个数,这就考虑了不同的放入顺序,从而得到了排列数。
  • 资源包:

    • 题目/文章: https://programmercarl.com/0377.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%E2%85%A3.html
    • 视频讲解: https://www.bilibili.com/video/BV1V14y1n7B6
我的实现:一维 DP (排列问题)
class Solution:def combinationSum4(self, nums: List[int], target: int) -> int:dp = [0] * (target + 1)dp[0] = 1# 先遍历背包,再遍历物品,求的是排列数for j in range(1, target + 1):for num in nums:if j >= num:dp[j] += dp[j - num]return dp[target]
3. LeetCode 70. 爬楼梯 (完全背包版)
  • 学习感悟: 用完全背包的视角重新审视“爬楼梯”问题,豁然开朗!
    • 背包模型: 背包容量是楼梯总数 n物品是每次能爬的步数(如 1 步、2 步,甚至 m 步)。因为每种步数可以重复使用,所以是完全背包。因为爬楼梯的顺序是重要的(先1后2 vs. 先2后1),所以是排列问题
    • DP 定义: dp[j] 表示爬到第 j 阶楼梯的方法总数。
  • 资源包: 代码随想录-70.爬楼梯(完全背包版)
我的实现:排列型完全背包
def solve_climbing_stairs(n, m):"""n: 楼梯总数m: 一次最多能爬的步数"""dp = [0] * (n + 1)dp[0] = 1# 先遍历背包(楼梯),再遍历物品(步数)for j in range(1, n + 1):for i in range(1, m + 1):if j >= i:dp[j] += dp[j - i]print(dp[n])

总结

今天我彻底搞清楚了完全背包和 0/1 背包的核心区别——一维 dp 数组的内层循环是正序还是倒序。更重要的是,我学会了通过调整内外层循环的顺序,来解决组合问题排列问题,这个技巧非常精妙。用新的视角重看旧问题,也让我对 DP 模型的理解更加深入。

http://www.lryc.cn/news/621271.html

相关文章:

  • opencv基础学习与实战(2)
  • 基于 LDA 模型的安徽地震舆情数据分析
  • Docker build创建镜像命令入门教程
  • 地测管理部绩效考核关键指标与地质数据分析
  • 码上爬第九题【协程+webpack】
  • C++基础(①入门教程)
  • K8s学习----Namespace:资源隔离与环境管理的核心机制
  • **标题:发散创新,探索编程中的平衡设计****摘要**:本文将探讨如何在编程中运用平衡设计思想,通过实例分析与
  • 37 C++ STL模板库6-string_view
  • 设计模式笔记_行为型_责任链模式
  • 仓颉编程语言的Any 类型(Any 接口)
  • Video-R1论文解读
  • 使用keil5 自带的仿真观察GPIO口波形
  • lib.dom.d.ts
  • 《量子雷达》第4章 量子雷达的检测与估计 预习2025.8.14
  • Windows bypassUAC 提权技法详解(一)
  • ACCESS多个时间段查询,只取整点,30分数据
  • 【读代码】深度解析 context-engineering-intro:开源上下文工程实践原理与应用
  • 【Functions】enumerate的用法
  • 机器学习-基础入门:从概念到核心方法论
  • Data Augmentation数据增强
  • 从0到1:C++ 语法之 nullptr
  • 机器学习内容总结
  • 机器学习初学
  • 前端vue框架
  • 机器学习知识总结
  • 智能体评测技术与实践:从评估维度到DeepEval实战指南
  • 20250814,通义万相,无限生成权限(慢速)
  • Linux中的日志管理
  • Linux中tty与8250-uart的虐恋(包括双中断发送接收机制)