目录
文章目录
题目概览
![]() | ![]() |
---|
第1题解答
从简单例子分析清楚问题
先明确清楚输入输出:
Input:
- 台阶个数 n n n
Output:
- 不同方式跳到n个台阶顶部的次数 m m m
当n=1时,仅有1种跳法,如下图:
当n=2时,可以跳2步1个台阶,也可以跳1步2个台阶,故两种跳法,如下图:
当n=3时,有3种跳法,如下图:
当n=4时,有5种跳法,如下图:
动态规划算法的分析过程
问题表达形式
如上枚举几个例子搞清楚问题结构之后,可以给出该问题更方便递推的表示形式:
记T[i]为不同方式跳到i个台阶的顶部的次数,那么原问题就是求T[n]
递推关系建立
就个人理解认为,动态规划的核心是利用递推关系找到该问题是否存在最优子结构,然后将原问题转换为多步决策问题,故接下来寻找原问题和子问题之间的递推关系。
以上述实例中的n=3为例进行递推关系分析:
如果青蛙第一步只跳1个台阶,可得下图:
从图中可以看出来,当青蛙第一步只跳1个台阶时,后面就剩2个台阶了,这相当于处理n=2得情况,从图中的红色虚线框也可看出,此时完全与n=2的情况是相同的。
如果青蛙第一步跳2个台阶,可得下图:
从图中可以看出来,当青蛙第一步跳2个台阶时,后面就剩1个台阶了,这相当于处理n=1得情况,从图中的绿色虚线框也可看出,此时完全与n=1的情况是相同的。
综上,n=3时,可以根据第一步跳1次还是跳2次分成两个子问题,用之前定义的问题表述形式可知,T[3]=T[2]+T[1]
同理,分析n=4时,可得下图:
综上图,n=4时,可以根据第一步跳1次还是跳2次分成两个子问题,用之前定义的问题表述形式可知,T[4]=T[3]+T[2]
综合上述分析可知,递推关系或者最优子结构为:
T
[
i
]
=
T
[
i
−
1
]
+
T
[
i
−
2
]
T[i]=T[i-1]+T[i-2]
T[i]=T[i−1]+T[i−2]
算法的自然语言描述
- 首先定义一个函数,将不同方式跳到 n n n个台阶顶部的次数定义为 T ( n ) T(n) T(n)
- 然后设置初始值,当 n = 1 n=1 n=1时,只有1种跳法,当 n = 2 n=2 n=2时,有2种跳法
- 然后构建一个循环,根据递推关系 T [ i ] = T [ i − 1 ] + T [ i − 2 ] T[i]=T[i-1]+T[i-2] T[i]=T[i−1]+T[i−2]逐步计算 T ( b ) T(b) T(b)的值
- 最后返回 T ( n ) T(n) T(n)的值即为所求的青蛙不同方式跳到 n n n个台阶顶部的次数
算法的复杂度分析
时间复杂度分析
主要消耗在一个遍历的循环,执行 n − 2 n-2 n−2次,故整个函数的时间复杂度为 O ( n ) O(n) O(n)
空间复杂度分析
主要消耗在存储 T [ i ] T[i] T[i]的数组上,这个数组的大小与 n n n成正比,故空间复杂度为 O ( n ) O(n) O(n)
参考资料
第2题解答
从简单例子分析清楚问题
题目中给的 n n n元预算、想买很多物品、价格为 v j v_j vj,重要程度为 w j w_j wj等这些概念都是抽象和普适的概念,绕来绕去容易给自己绕进去,故秉持着简单的原则,给上述抽象概念都赋上具体的值,以一个具体的例子来弄清楚整个问题。

如上图所示,假设想买的物品有啤酒、面包、饼干、牛奶,各自的价格、重要程度以及价值都如图所示。为分析清楚问题,可以先通过最直接的蛮力枚举法,观察该问题的结构。
先不考虑预算限制,只从枚举的角度,枚举出所有的情况:

图中蓝色粗线为货架,平板车为购物车,故可知总共有 C 4 0 + C 4 1 + C 4 2 + C 4 3 + C 4 4 C^0_4+C^1_4+C^2_4+C^3_4+C^4_4 C40+C41+C42+C43+C44中选法,通过预算为 n n n的限制可以去除一些情况,然后在剩下的情况中选出最大价值的方案。
动态规划算法的分析过程
问题表达形式
经过上述简单例子的分析,可以很直观的感受到,这是一个典型的$ 0 - 1 背包问题的变体。我们有 背包问题的变体。我们有 背包问题的变体。我们有 n 元预算,每个物品有价格 元预算,每个物品有价格 元预算,每个物品有价格 v_j $和价值 (重要程度 w j w_j wj 与价格 $v_j $ 的乘积),需在预算限制内选择物品以最大总价值。那么可给出该问题的形式化定义:
递推关系的建立
观察之前枚举时候的例子,分析原问题与子问题之间是否存在最优子结构,如下图:

从上图中可以看出,原问题可以分为买牛奶和不买牛奶两种情况:

往下递推又可分为买饼干和不买饼干两种情况,故可以如下定义一个递归函数:

有了上述函数之后,便可给出多步决策的最优子结构:
B
u
y
(
j
,
b
)
=
m
a
x
{
B
u
y
(
j
−
1
,
b
−
v
j
)
+
v
j
×
w
j
,
B
u
y
(
j
−
1
,
b
)
}
\mathbf{Buy}(j, b)=max\{\mathbf{Buy}(j-1, b-v_j)+v_j\times w_j,\mathbf{Buy}(j-1, b)\}
Buy(j,b)=max{Buy(j−1,b−vj)+vj×wj,Buy(j−1,b)}
那么原问题就可表达为
B
u
y
(
k
,
n
)
\mathbf{Buy}(k, n)
Buy(k,n)。
算法的自然语言描述
- 初始化阶段
- 首先,我们需要获取预算和物品的数量。
- 然后,对于每个物品,记录它的价格和重要程度。
- 同时,创建一个二维数组用于存储在不同物品数量和不同预算下所能获得的最大价值,初始时将二维数组中所有元素设为 0 (代表没有物品或没有预算时价值为 0)。
- 填充动态规划表
- 使用两个嵌套的循环。外层循环遍历每个物品 (从第 1 个物品到最后一个物品),内层循环遍历每个可能的预算值 (从 1 到预算 n )。
- 对于二维数组中的每一个 (i 表示物品序号, j 表示预算),首先假设不选择当前物品 ,那么当前位置二维数组的值就等于前物品在当前预算下的最大价值。
- 然后,检查如果选择当前物品(前提是当前预算大于等于物品的价格),那么可以通过前物品在当前预算下的最大价值加上当前物品 i 的价值。逐步填充整个表。
- 回溯追踪最优解
- 当动态规划表填充完成后,我们从最后一个物品和预算 n 开始回溯。最后得到的选择的物品列表就是在预算$ n $内价值最大化的购物清单。
算法的时空复杂度分析
时间复杂度
初始化二位动态规划数组的时间复杂度为 O ( m n ) O(mn) O(mn),其中$ n 是预算, 是预算, 是预算, m $是物品数量。然后有两个嵌套的循环,外层循环遍历 $m 个物品,内层循环遍历 个物品,内层循环遍历 个物品,内层循环遍历 n 个预算值。在每次内层循环中的计算需要常数时间操作 ( 主要是比较和简单计算 ) 。所以填充表的时间复杂度为 个预算值。在每次内层循环中的计算需要常数时间操作 (主要是比较和简单计算)。所以填充表的时间复杂度为 个预算值。在每次内层循环中的计算需要常数时间操作(主要是比较和简单计算)。所以填充表的时间复杂度为O(nm) 。回溯过程最多遍历 m 个物品,时间复杂度为 。回溯过程最多遍历 m 个物品,时间复杂度为 。回溯过程最多遍历m个物品,时间复杂度为O(m) 。综合来看,总的时间复杂度为 。综合来看,总的时间复杂度为 。综合来看,总的时间复杂度为O(nm)$。
空间复杂度
主要的空间占用是动态规划二维数组,其大小为 ( m + 1 ) × ( n + 1 ) (m+1)\times(n+1) (m+1)×(n+1),其中$ m 是物品数量, 是物品数量, 是物品数量, n 是预算。所以空间复杂度为 是预算。所以空间复杂度为 是预算。所以空间复杂度为O(nm)$。
第3题解答
先从简单例子理解清楚问题
当 n = 2 n = 2 n=2 且 S = { 1 , 2 } S = \{1, 2\} S={1,2} 时,如下图:
此时由两个nice子数组分别为[1, 1]和[2, 2]。
当 n = 3 n=3 n=3 且 S = { 1 , 2 , 3 } S=\{ 1, 2,3\} S={1,2,3}时,如下图:

此时,可以看出一些规律,n=3时,如果不拆的话,[3, 3, 3]是一种情况,如果只拆一次的话,单个1是满足nice子数组特性的,另一半只要凑成[a, b],a,b≥2且不能同时为3即可,然后交换位置之后乘2即可,故此时总共有3+3+2=8种。
当 n = 5 n=5 n=5 且 S = { 1 , 4 , 5 } S=\{ 1, 4,5\} S={1,4,5}时,如下图:
同理,如果不拆的话,[5, 5, 5, 5, 5]是1种情况,如果全拆的话,[1, 1, 1, 1, 1]是1种情况,如果只拆1次的话,单个[1]满足子数组特征,而另一半无法组成以5为最小值的子数组,只能凑以4为最小值的子数组,原问题变成了 n = 4 n=4 n=4 , S = { 4 , 5 } S=\{4, 5\} S={4,5}, 此时由于S中是4,5,故已经无法再拆了,要满足子数组特征的话,[a, b, c, d], a,b≥4且不能同时为5即可。
动态规划算法的分析过程
从上面三个例子的分析过程可知,
S
S
S中大于
n
n
n的元素是无法作为子数组中的最小值的,故只需考虑
S
S
S中小于等于
n
n
n的元素。对于
S
S
S中的第
i
i
i个元素
S
[
i
]
S[i]
S[i], 该元素的nice子区间中必须只要有一个
S
[
i
]
S[i]
S[i],且其他元素大于等于
S
[
i
]
S[i]
S[i],根据上面的实例可以给出不论
n
n
n是多少,只要
S
S
S确定了,那么以其中某一个元素
S
[
i
]
S[i]
S[i]为最小值的nice数组的数目都是可以确定的,若
S
S
S中有
m
m
m个元素且递增,那么以
S
[
i
]
S[i]
S[i]为最小值的nice数组的个数
N
i
c
e
[
i
]
Nice[i]
Nice[i]为:
N
i
c
e
[
i
]
=
(
m
−
i
+
1
)
S
[
i
]
−
(
m
−
i
)
S
[
i
]
Nice[i]=(m-i+1)^{S[i]}-(m-i)^{S[i]}
Nice[i]=(m−i+1)S[i]−(m−i)S[i]
上式中的前半部分是指大于等于
S
[
i
]
S[i]
S[i]的所有情况,后半部分是把大于
S
[
i
]
S[i]
S[i]的所有情况减掉,所以最后的到是至少包含一个
S
[
i
]
S[i]
S[i]的nice数组的数量。
有了上述分析之后可知,如果
S
S
S给定了,比如
S
=
{
1
,
2
}
S=\{1,2\}
S={1,2}或
S
=
{
1
,
4
,
5
}
S=\{1,4,5\}
S={1,4,5}等,那么
S
S
S中的所有元素
S
[
i
]
S[i]
S[i]对应的以其为最小值的nice数组的个数
N
i
c
e
[
i
]
Nice[i]
Nice[i]是已知的。那么原问题就可以变为怎么通过这些nice数组拼接成一个长度为
n
n
n的数组
a
[
1..
n
]
a[1..n]
a[1..n],使用
D
p
[
j
]
Dp[j]
Dp[j]来表示长度为
j
j
j的数组的nice子数组的个数,那么根据上述分析得递推关系:
D
p
[
j
]
=
∑
D
p
[
j
−
S
[
i
]
]
×
n
u
m
[
i
]
Dp[j]=\sum Dp[j-S[i]]\times num[i]
Dp[j]=∑Dp[j−S[i]]×num[i]
算法自然语言描述
查看集合$ S $里的每个元素。对于每个元素,计算它作为最小值时nice子数组的数量。先算出大于等于这个元素的所有情况数,再减去大于这个元素的所有情况数,得到的就是至少有一个这个元素作为最小值的nice子数组数量。
接着从长度为1的数组开始遍历到长度为 n n n的数组,把长度为当前长度减去这个元素值的数组的nice子数组数乘以这个元素对应的nice子数组数,然后累加到长度为当前长度的数组的nice子数组数上。最后得到满足条件的nice子数组的个数。
算法的时空复杂度分析
时间复杂度
在动态规划过程中,有两层循环。外层循环遍历数组长度 j j j从 1 1 1到 n n n,对于每个 j j j,内层循环遍历集合 S S S中满足 S [ i ] ≤ j S[i]\le j S[i]≤j的元素。在最坏情况下,对于每个 j j j,内层循环都要遍历整个集合 S S S,即 m m m次。所以动态规划这部分的时间复杂度是 O ( m n ) O(mn) O(mn)。
空间复杂度
需要存储计算得到的 N i c e Nice Nice数组,其长度为 m m m,用于保存每个元素对应的 nice数组个数。还需要一个数组 D p Dp Dp来存储长度为 j j j的数组的 “nice” 子数组个数,其长度为 n + 1 n+1 n+1,故总的空间复杂度是 O ( n + m ) O(n+m) O(n+m)
第4题解答
算法分析过程
这是一个带有依赖关系 (配件依赖于主物品)的背包问题。需要考虑在预算限制下选择主物品和其相应配件以最大化总价值。
有第二题的铺垫之后,可以直接上手,首先定义 d p [ i ] [ j ] dp[i][j] dp[i][j]表示在前 i i i个物品组合中,预算为 j j j时能获得的最大总价值。其中 i i i的范围是从 1 1 1到 m m m, j j j的范围是从 0 0 0到 n n n。对于第 i i i个物品组合,有两种选择:选或不选。不选第 i i i 个物品组合时: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i−1][j],即保持前 i − 1 i-1 i−1个物品组合在预算 j j j下的最大价值。选第 i i i 个物品组合时(前提是预算足够,即 j ≤ v i j\le v_i j≤vi):,意思是在前 i − 1 i-1 i−1个物品组合在预算 j − v i j-v_i j−vi的最大价值基础上,加上当前物品组合的价值。
可以递推关系为:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
]
if
j
<
v
i
max
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
v
i
]
+
w
i
)
if
j
≥
v
i
d p[i][j]= \begin{cases}d p[i-1][j] & \text { if } j<v_i \\ \max \left(d p[i-1][j], d p[i-1]\left[j-v_i\right]+w_i\right) & \text { if } j \geq v_i\end{cases}
dp[i][j]={dp[i−1][j]max(dp[i−1][j],dp[i−1][j−vi]+wi) if j<vi if j≥vi
算法自然语言描述
首先获取预算$ n 和物品的总数。然后,将每个物品的信息输入,包括价格、重要程度、是否为主物品以及配件的索引信息 ( 如果有 ) ,并存储在合适的数据结构中 ( 比如列表 ) 。创建一个二维数组动态规划表,其行表示物品的索引 ( 从 0 到物品总数 ) ,列表示预算 ( 从 0 到 和物品的总数。然后,将每个物品的信息输入,包括价格、重要程度、是否为主物品以及配件的索引信息 (如果有),并存储在合适的数据结构中 (比如列表)。创建一个二维数组动态规划表 ,其行表示物品的索引 (从 0 到物品总数),列表示预算 (从 0 到 和物品的总数。然后,将每个物品的信息输入,包括价格、重要程度、是否为主物品以及配件的索引信息(如果有),并存储在合适的数据结构中(比如列表)。创建一个二维数组动态规划表,其行表示物品的索引(从0到物品总数),列表示预算(从0到 n $)。将二维数组动态规划表中的所有元素初始化为 0 ,这代表在没有物品或没有预算时,总价值为 0 。开始遍历每个物品和每个预算值填充动态规划。判断是否是主物品,与第二题相同。如果该主物品有配件,分不同情况处理。若有两个配件 (配件索引为 i + 1 和 i + 2 类似):在预算 $j 足够支付主物品和两个配件价格的情况下,当前二维数组动态规划表的值可以通过前 足够支付主物品和两个配件价格的情况下,当前二维数组动态规划表的值可以通过前 足够支付主物品和两个配件价格的情况下,当前二维数组动态规划表的值可以通过前 i - 1 个物品在预算下的最大价值加上主物品、两个配件的价值总和来更新若有一个配件 ( 配件索引为 个物品在预算下的最大价值加上主物品、两个配件的价值总和来更新若有一个配件 (配件索引为 个物品在预算下的最大价值加上主物品、两个配件的价值总和来更新若有一个配件(配件索引为 i + 1$ ):在预算$ j 足够支付主物品和配件价格的情况下,的值可以通过前 足够支付主物品和配件价格的情况下, 的值可以通过前 足够支付主物品和配件价格的情况下,的值可以通过前 i - 1 $个物品在预算下的最大价值加上主物品和配件的价值总和来更新,如果不是主物品 (即配件):由于配件不能单独选择,其价值依赖于对应的主物品选择,所以在这个阶段不单独处理配件,直接跳过。
算法时空复杂度分析
时间复杂度分析
计算二维数组动态规划表需要遍历 m m m个物品组合和预算 n n n,对每个位置的计算都是常数时间,所以时间复杂度为 O ( m n ) O(mn) O(mn)
空间复杂度分析
主要消耗在二维数组动态规划表上,其空间复杂度为 O ( m n ) O(mn) O(mn)