赞
踩
动态规划常常用在一些贪心算法无法得到正确答案的题目上,因为动态算法对思维方面有一定要求而且代码量比较小,在笔试题中特别常见。
动态规划算法的问题有下面4个特点:
比如我们在解决一个剪绳子的问题时(题目来自剑指offer):给一个int n长的绳子分为int m段,求剪开的每段绳子的乘积最大是多少。比如长3的绳子分为2段,可以剪为长1,长2的两段,最大乘积就是1 × 2 = 2 。在用动态规划方法思考这个问题的时候,我们每一次剪开绳子面临若干个选择,比如对于n长的绳子我们在剪第一刀的时候有n - 1个选择:剪在位置1,2,3...n-1。由于我们不知道那个位置是最优解,所以我们只能都尝试一遍,然后寻找最大值。而且我们发现,剪绳子这个问题可以分解为若干个小问题,这些小问题之间有相互重叠的更小的问题:如果绳子总长为8 也就是求解问题f(8),我们可以把他剪成3和5两段。然后分别求解这两个问题:f(3) f(5),在求解 f(5) 的时候我们又能分为长为2和长为3的两段,也就是问题 f(2) f(3),问题f(3) 可能被分解为问题 f(2) f(1)。也就是说问题 f(5) 包含问题 f(3)而且还包含问题f(3) 的子问题。这就是上文中提到的动态规划算法的前3个特点。
对于特点4我们看下面的例子:
这是一个很典型的例子,我们写斐波那契数列时最直观的算法就是利用递归:
- long long Fibonacci(unsigned int n) {
- if (n <= 0) return 0;
-
- if (n == 1) return 1;
-
- return Fibonacci(n - 1) + Fibonacci(n - 2);
- }
但是很明显递归不是一个很有效的算法,求解 f(3) 会用到 f(1) 和 f(2),求解 f(4) 也会用到 f(1) 和 f(2),但是在递归的方法中我们在求解 f(5) 时多次重复计算 f(1) 和 f(2) 这两个值,也就是说在求解一个大问题 f(5) 时,这个大问题可以被分解为两个小问题求解 f(3) f(4),这两个小问题又包含很多跟小的问题 f(1) 和 f(2) ,所以我们通过把小问题的解储存下来,从小往大求解:
- long long Fibonacci2(unsigned int n) {
- if (n <= 0) return 0;
- if (n == 1) return 1;
- if (n == 2) return 1;
-
- int tmp1 = 1;
- int tmp2 = 1;
- int tmp3 = 0;
-
- for (int i = 2; i < n; i++) {
- tmp3 = tmp1 + tmp2;
- tmp1 = tmp2;
- tmp2 = tmp3;
- }
- return tmp3;
- }
给定如图(图片来自TheOneGIS的博客https://blog.csdn.net/theonegis/article/details/45801201)一个数塔,从顶部出发在每一个节点可以选择向左或者向右走,一直走到底层,要求找出一条路径,使得路径上的数字之和最大。
用data数组保存输入,直接修改data数组保存从每个节点出发一直到底层的最大路径。最低一层的最大路径就是原值所以直接跳过。
对第 i 层( i from layer - 2 to 0 ) 的第 j 项( j from 0 to i + 1 ) (比如对第4层的第 0, 1, 2, 3 项)比较其左右两个子节点的值,然后把最小值加上改节点的路径长度保存在该节点中:
data[i][j] += max(data[i + 1][j], data[i + 1][j + 1]);
- #include <iostream>
-
- using namespace std;
-
- int solution(int** data, int layer) {
- int temp_max = 0;
- for (int i = layer - 2; i >= 0; --i) {
- for (int j = 0; j< i + 1; ++j) {
- temp_max = max(data[i + 1][j], data[i + 1][j + 1]);
- data[i][j] += temp_max;
- }
- }
-
- return data[0][0];
- }
-
- int main() {
-
- cout << "几层?" << endl;
-
- int layer = 5;
- cin >> layer;
-
- cout << "输入塔的值" << endl;
- int** data = new int*[layer];
- for (int i = 0; i < layer; ++i) {
- data[i] = new int[layer];
- }
- for (int i = 0; i < layer; ++i) {
- for (int j = 0; j < layer; ++j) {
- data[i][j] = 0;
- }
- }
-
- for (int i = 0; i < layer; ++i) {
- for (int j = 0; j < i + 1; ++j) {
- int tmp;
- cin >> tmp;
- data[i][j] = tmp;
- }
- }
-
- solution(data, layer);
-
- for (int i = 0; i < layer; ++i) {
- for (int j = 0; j < layer; ++j) {
- cout << data[i][j] << " ";
- }
- cout << endl;
- }
-
- for (int i = 0; i < layer; ++i) {
- delete[] data[i];
- }
- delete[] data;
-
- return 0;
- }
给予无限多不同面值的硬币若干种,如何用若干种硬币组合为某种面额的钱,使硬币的的个数最少(如果无法组合就输出999999)
题目中加入我们要组合为20元,可能会分为先组合1元再组合19元、先组合2元再组合18元......很多种情况,根据从小往大解决问题的思路,我们先解决组合为1元,组合为2元的问题,然后逐级向上求解
在下面代码的第一个 for 循环是将result数组中可以有对应面值的组合的最少硬币个数置1(比如我们提供了面值为10的货币,result[10] = 1,因为支付10元时直接付一张10块钱的RMB就可以啦)
第二个 for 循环用来更新需要支付某一值的钱数时需要的最少硬币,无解就保持数组默认值MAX不变,在扫描时假如当前求解需要支付8元的情况就遍历求解 f(1) + f(7), f(2) + f(6), f(3) + f(5), f(4) + f(4) 然后选择其中的最小值更新 f(8) 的值
- #include <iostream>
-
- #define MAX 999999
-
- using namespace std;
-
- void solution(int kind, const int* value, int cost, int* result) {
- for (int i = 0; i < kind; ++i) {
- result[value[i]] = 1;
- }
-
- int tmp;
- for (int i = 1; i <= cost; ++i) {
- for (int j = 1; j <= int(i / 2); ++j) {
- tmp = result[j] + result[i - j];
- if (tmp < result[i]) result[i] = tmp;
- }
- }
- }
-
- int main() {
-
- cout << "几种?" << endl;
- int kind = 0;
- cin >> kind;
-
- cout << "输入面值" << endl;
- int* value = new int[kind];
- for (int i = 0; i < kind; ++i) {
- cin >> value[i];
- }
-
- cout << "输入饮料价值" << endl;
- int cost;
- cin >> cost;
- int* result = new int[cost + 1];
- for (int i = 0; i <= cost; ++i) {
- result[i] = MAX;
- }
-
- solution(kind, value, cost, result);
-
- for (int i = 0; i <= cost; ++i) {
- cout << result[i] << " ";
- }
-
- delete[] value;
- delete[] result;
-
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。