0%

Dynamic Programming

Those who cannot remember the past are condemned to repeat it.

动态规划(Dynamic Programming)算法的核心是记住已经解决过的字问题的解。如何理解呢?举个例子:

假如我们要求斐波那契数列中的第n个数,一种简单的求法是:

1
2
3
4
5
6
7
8
int fib(int &n)
{
if (n <= 1) {
return n;
}

return fib(n-1) + fib(n-2);
}

显然,借助递归来求斐波那契数列,其复杂度是指数级的,如果要降低复杂度呢?很简单,利用动态规划:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <vector>

int fib(int &n)
{
std::vector<int> f;
f[0] = 0;
f[1] = 1;

for (int i = 2; i <=n; ++i) {
f[i] = f[i-1] + f[i-2];
}

return f[n];
}

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例:
输入:[1, 2, 3, 1]
输出:4 = 1 + 3

对于小偷来说,需要考虑当前的房子能够带来的收益,同时,至少到第3间房,才能开始动手,故而前两间房子的初始收益可以设置为dp = {0, 0}

接下来,如果偷一间房,那么其收益为dp[i - 2] + 当前房子的收益,反之,则收益为dp[i - 1]。故而,其转移状态方程为:

1
dp[i] = max(dp[i - 2] + nums[i - 2], dp[i - 1]);

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
int rob(vector<int> &nums)
{
vector<int> dp(nums.size() + 2, 0); // {0, 0, 0, ... , 0}

for (int i = 2; i < nums.size() + 2; ++i) {
dp[i] = max(dp[i-2] + nums[i-2], dp[i-1]);
}

return dp[nums.size()];
}
};

再来看著名的硬币问题,即给定一定数量的硬币[1, 5, 10],针对自然数N,给出其所有可能的组合方案,比如:

1
2
3
4
5
6
7
8
9
Input:  N = 12
coins = [1, 5, 10]

Output: 4

Explanation: 1 way: 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 12
2 way: 1 + 1 + 1 + 1 + 1 + 1 + 5 + 1 + 1 = 12
3 way: 5 + 5 + 1 + 1 = 12
4 way: 10 + 1 + 1 = 12

从动态规划的角度看,5个1便士可以换成1个5便士,10个1便士可以换成2个5便士或1个10便士,那么针对自然数N以内的所有求解方案,都可以基于该换算进行。比如4便士只能由4个1便士组成,6便士则既可以用6个1便士,也可以将前5个1便士替换为1个5便士,那么自然数12以内的组合如下所示:

1
2
3
4
5
6
7
8
9
N: 1  2  3  4  5  6  7  8  9  10 11 12

1: 1 1 1 1 1 1 1 1 1 1 1 1

2: 0 0 0 0 5 1 1 1 1 1 1 1 ($5=1 \times 5$)

3: 0 0 0 0 5 0 0 0 0 5 1 1

4: 0 0 0 0 0 0 0 0 0 10 1 1 ($10= 1 \times 10=5 \times 2$)

接下来,用C++代码求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>

unsigned long long countChange(const unsigned int money, const std::vector<unsigned int>& coins) {
std::vector<unsigned long long> ways(money + 1, 0);

// Initialization
ways[0] = 1;

for (auto &coin : coins) {
for (int i = 0; i < money + 1; ++i) {
if (coin <= i) {
ways[i] += ways[i - coin];
}
}
}

return ways[money];
}