最简单的说法就是,将一个大问题分成无数小问题,最后通过小问题得出大问题的解答
重点在于是否能根据题目得出动态转移的方程
基本原理
1.最优子结构
最优子结构是指一个问题的整体最优解可以通过利用其子问题的最优解来获得。换句话说,问题的最优解包含了其子问题的最优解。
在动态规划中,我们通常将原问题分解为若干个子问题,并从底层的子问题开始逐步解决,将子问题的解保存起来,供后续使用。这种自底向上的解决方式可以避免重复计算,从而大幅提高算法效率。
必须满足有以下两个条件:
- 原问题能够分解成子问题:原问题能分解成更小相同类型的子问题
- 子问题的最优解能够变为原问题的最优解: 即通过子问题的最优解来得出原问题的最优解
2.重叠子问题
由于是拆分为了多个小问题,因此有很大可能会造成数据的重复使用,但又存储,进而造成效率低下
- 备忘录法: 使用一个数据结构来存储之前运算的结果,类似数组
- 自下而上: 从最小的最开始的子问题计算
背包问题
0-1背包问题
问题描述
有一个容量为 V 的背包,和n件物品。这些物品分别有两个属性:体积 w 和价值 v ,且每种物品都只有一个。现要求将这些物品在不超过容量 V的前提下装入背包中,并使得此背包的价值最大。问该最大值是多少?
注:由于在该问题的所有解中,每个物品只有两种可能的情况:在背包中、不在背包中(即背包中的任意物品数量只能为 0 或 1),因此该问题被称为 0-1 背包问题。
算法分析
假如当前背包最大容量只有4,尝试得出装一下物品最大数量
物品 | 价值 v | 重量 w |
A | 15 | 1 |
B | 30 | 4 |
C | 20 | 3 |
最简单即最无脑的办法就是列举出所有的组合方案,从而得出最大价值的组合(穷举法)
但是穷举法一遇到数据量大的情况时就会哑火
通过使用动态规划,使得将大问题分为小问题(子问题)
首先判断A这一行,即在不同的背包容量下是否能选择A
V = 1时,由于A重量为1 可以选取
V = 2时,由于A重量为1 可以选取
V = 3时,由于A重量为1 可以选取
V = 4时,由于A重量为1 可以选取
1 | 2 | 3 | 4 | |
A | 15 | 15 | 15 | 15 |
B | ||||
C |
再来判断B这一行,即在不同的背包容量下是否能选择B,但条件发生了改变,当前背包容量下,是否还能选择A
V = 1时,由于B重量为4 不可以选取,但能选择A,因此是15
V = 2时,由于B重量为4 不可以选取,同上
V = 3时,由于B重量为4 不可以选取,同上
V = 4时,由于B重量为4 不可以选取,由于此时A也可以选,B也可以选,则比较两者价值,发现B更大,则选B
1 | 2 | 3 | 4 | |
A | 15 | 15 | 15 | 15 |
B | 15 | 15 | 15 | 30 |
C |
判断C这一行
V = 1时,由于C重量为3 不可以选取,但能选择A,因此是15
V = 2时,由于C重量为3 不可以选取,但能选择A,因此是15
V = 3时,由于C重量为3 可以选取,但也能选择A,因此根据价值判断是选C
V = 4时,考虑的情况变多,可以是选B,可以是A+C 根据价值判断,是选35
1 | 2 | 3 | 4 | |
A | 15 | 15 | 15 | 15 |
B | 15 | 15 | 15 | 30 |
C | 15 | 15 | 20 | 35 |
由此可以得出以下公式
dp[i][j] =max(上方单元格的价值,剩余空间的价值+当前商品的价值)
=max(dp[i−1][j],dp[i−1][j−当前商品的体积]+当前商品的价值)
=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i])
空间优化
空间优化也是背包问题的经典优化,将dp这种二维数组优化为一维数组
根据公式可以看出,实际上dp[i][j]的值只和他上方和左上方的值有关联,自然而然想到可以直接从数组后端往前端运算,这样就可以变为一维数组
实际例题
Leetcode 416 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
分析一下,使得两个子集的元素和相等,即只要有一个子集能够等于总和的一半,即可满足题目要求
每一个元素的状态也只有选/不选两种情况,与0-1背包问题意思相近
尝试用dp[i][j]来表示一个子问题,即选择[0 -- i-1]中能否通过选择几个元素使得他们的和为j,这样dp[len][target]就会是所有元素中选择几个元素是否能使得他们的和变为target(数组和的一半)
考虑当前元素是否选择,若选择,则代表dp[i-1][j - num[i]]会是true(即前面的选择中能够使和为j-num[i] 再选上当前元素即可 和变为j)
若不选择,则dp[i-1][j] 就是dp[i][j]的情况
特殊情况: nums[i]即是j