- 记录和汇总动态规划经典题目
买苹果
Description
- 动态规划-买苹果 | 牛客网
- 小易去附近的商店买苹果,奸诈的商贩使用了捆绑交易,只提供 6 个每袋和 8 个每袋的包装(包装不可拆分)。 可是小易现在只想购买恰好
n
个苹果,小易想购买尽量少的袋数方便携带。如果不能购买恰好n
个苹果,小易将不会购买。 - 输入描述: 输入一个整数n,表示小易想购买
n(1 ≤ n ≤ 100)
个苹果 - 输出描述: 输出一个整数表示最少需要购买的袋数,如果不能买恰好
n
个苹果则输出-1
- 测试用例
1 | //input |
Approach 1-动态规划(通解)
Analysis
- 采用动态规划方法求解。
- 创建一个
vector
容器steps
,steps[i]
表示购买i
个苹果所需的最小袋数。 - 初始化为
steps
容器为INT_MAX
。 - 从购买1个苹果开始遍历,若
steps[i]
为INT_MAX
,表示无法购买该个数的苹果,直接开始下次循环。 - 若
steps[i]
不为INT_MAX
,表示该个数的苹果可以购买,进行动态规划求解。 - 动态规划的转移方程为
1 | steps[i+j] = min(steps[i]+1,steps[i+j]) //j为6或8 |
- 动态规划的过程如下图所示
Solution
- C++
1 |
|
Approach 2-贪婪算法
Analysis
- 采用贪婪算法求解。
- 优先选取每袋含有 8 个苹果的包装。若还有余数,则再用 6 个装的包装去购买。
- 如果不行的话,则将 8 个装的个数减去 1 个,进行回溯,再用 6 包装的去购买。
- 如果还不行的话,再次回溯,直到购买 8 包装的个数为 0。
贪婪算法并不一定能得到最优解,但是一个可行的,较好的解。 例如,给定硬币
coins=[1,2,10,25]
,金额总数amounts=30
,不限制每种币值的硬币数量,要求用所给硬币凑出所需金额,并且硬币数量最少。若采用贪婪算法求解,需要 6 枚(25+5*1)硬币。 若采用动态规划求解,所需 3 枚(10+10+10)硬币。
下面对使用贪婪算法能否得到最优解进行分析。
- 首先,6 和 8 都是偶数。因此,能凑出的个数也一定是偶数。程序中若苹果总数是奇数,可以直接返回
-1
。 - 再次,偶数个苹果数对 8 取模,其结果只可能为
0,2,4,6
。 - 若余数为 6 或者 0,则可以直接用 6 包装情况处理,不需要回溯购买 8 包装的情况。
- 若余数为 4,只需回溯 1 次即可,因为
8+4=12
,12%6=0
。 - 若余数为 2,只需回溯 2 次即可,因为
8+8+2=18, 18%6=0
。
综上,本题情况使用贪婪算法一定能得到最优解。
Solution
- C++
1 | #include <iostream> |
Approach 3-数字分析求解
Analysis
- 对数字特征进行分析。
- 首先,6 和 8 都是偶数。因此,能凑出的个数也一定是偶数。程序中若苹果总数是奇数,可以直接返回-1。
- 再次,偶数个苹果数对 8 取模,其结果只可能为
0,2,4,6
。 - 若余数为 6 或者 0,则可以直接用 6 包装情况处理,不需要回溯购买 8 包装的情况。
- 若余数为 4,只需回溯 1 次即可,因为
8+4=12, 12%6=0
。 - 若余数为 2,只需回溯 2 次即可,因为
8+8+2=18, 18%6=0
。
综上,可以采用如下思路进行处理。(由于数字 6 和 8 的特征,本方法只适用于本题,不具有通用性,动态规划为本题通用解法)
- 情况1:若
num
不是偶数,则直接返回-1
- 情况2:若
num%8=0
,则返回num/8
- 情况3:若
num%8 !=0
,则只需回溯 1 次或者 2 次 8 包装购买个数,就可以求解。- 回溯 1 次,其结果为
n/8-1+2= n/8+1
- 回溯 2 次,其结果为
n/8-2+3 = n/8+1
- 因此,可以情况3下,可以返回
n/8+1
- 回溯 1 次,其结果为
Solution
- C++
1 |
|
跳石板
题目描述
小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3…….
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。
- 例如
1 | N = 4,M = 24: |
于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板。
- 输入描述
1 | 输入为一行,有两个整数N,M,以空格隔开。 (4 ≤ N ≤ 100000) (N ≤ M ≤ 100000) |
- 输出描述
1 | 输出小易最少需要跳跃的步数,如果不能到达输出-1 |
- 测试用例
1 | //输出 |
Approach 1-动态规划
Analysis
采用动态规划思想求解。创建一个 vector
容器 steps
,steps[i]
表示到达 i 号石板所需的最小步数。
- 初始化为
steps
容器为INT_MAX
。 - 从序号 N 的石板开始逐个遍历,若
steps[i]
为INT_MAX
,表示该点不可到达,直接开始下次循环。 - 若
steps[i]
不为INT_MAX
,表示该点可以到达。
动态规划的转移方程为
1 | steps[i] = INT_MAX //初始化所有值为INT_MAX |
Solution
下面给出代码实现
- C++
1 |
|
此处给出一个常规的优化项说明,上述代码在判断 for
循环时,限制循环终止条件为 (j*j)<=i
,对于大于 sqrt(i)
的约数,在同一个for循环中进行判断,即
1 | for(int j=2;(j*j)<=i;j++){ |
上述可以明显减少for循环次数,针对N=4,M=24
的情况,上述代码会执行10次循环。如果使用下述代码,将会执行15次代码(在牛客网系统上,会被当做超时处理)
1 | for(int j=2;j<i;j++){ |
Approach 2-贪婪算法
Analysis
(本题目,使用贪婪算法并不能AC,此处给出的贪婪算法,仅作为一个示例给出,用于分析贪婪算法的使用场景)
贪婪算法并不一定能得到最优解,但是一个可行的,较好的解。
该问题若采用贪婪算法求解,并不会得到最优解,只会得到一个可行的,较好的解。例如,下述程序中采用了贪婪算法求解。每次都选取最大的约数前进一步。若后续发生不可到达目标点,则进行回溯,取第2大的约数作为步进值。下述程序通过率为80%,并不能AC。例如,对于N=676, M=12948情况,贪婪算法求解为13步,而动态规划算法求解为10步。
贪婪算法并不一定能得到最优解,但是一个可行的,较好的解。例如,给定硬币coins=[1,2,10,25],金额总数amounts=30,不限制每种币值的硬币数量,要求用所给硬币凑出所需金额,并且硬币数量最少。若采用贪婪算法求解,需要6枚(25+5*1)硬币。 若采用动态规划求解,所需3枚(10+10+10)硬币。 — 贪婪算法
Solution
1 | // 程序通过率为80%,并不能AC |