每天一种算法分析-动态规划

动态规划常常用在一些贪心算法无法得到正确答案的题目上,因为动态算法对思维方面有一定要求而且代码量比较小,在笔试题中特别常见。

算法思想

动态规划算法的问题有下面4个特点:

  1. 算法目的是求一个最优解(最大值或者最小值)
  2. 该问题可以被分解为若干个小问题,这若干个小问题之间包含很多共同的更小的问题
  3. 大问题的最优解依赖与小问题的最优解
  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;
}

 

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值