1. 动态规划原理
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
例如背包问题,他可以通过递归求解。假设给定 N N N个物品列表重量分别为
{ W 1 , W 2 , W 3 , W 4 , . . . , W N } \{
{W_1},{W_2},{W_3},{W_4},...,{W_N}\} {
W1,W2,W3,W4,...,WN},给定背包容量为 V V V,列举背包可以放入物品的所有情形。这里假定每个物品只有1个。
利用递归的思想可以这么求解:
首先任取 N N N个物品中的一个物品,其重量为 W i {W_i} Wi,若 W i < V {W_i} < V Wi<V,则可以将物品 i i i放入背包,同时对剩下 N − 1 N-1 N−1个物品,通过递归求解其是否能放入体积为 V − W i V-{W_i} V−Wi的背包。
事实上,进行递归求解时会存在很多重复计算。假设 W i , W j {W_i},{W_j} Wi,Wj以及 W i + W j {W_i}+{W_j} Wi+Wj均小于 V V V。很显然递归求解容积为 W i + W j {W_i}+{W_j} Wi+Wj解时会重复计算一次容积分别为 W i , W j {W_i},{W_j} Wi,Wj的解。这就存在着很多重复计算。
而用动态规划思想去解决背包问题,原理就是将容积从小变大,依次求解 V = { 1 , 2 , 3 , 4 , . . . , V } V=\{
{1},{2},{3},{4},...,{V}\} V={
1,2,3,4,...,V}的所有解。当存在容积 V i {V_i} Vi可以分解成多个子容积时,其解也与子问题的解有对应关系,可以直接将之前计算的结果应用于当前容积,以避免重复计算。
2.实例讲解
以Leetcode第39题为例,分别介绍递归法与动态规划法如何实现。题目介绍如下:
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
递归法的思路比较简单,以 c a n d i d a t e s = [ 2 , 3 , 6 , 7 ] , t a r g e t = 7 candidates = [2,3,6,7], target = 7 candidates=[2,3,6,7],target=7为例,首先 7 7 7在数组 c a n d i d a t e s = [ 2 , 3 , 6 , 7 ] candidates = [2,3,6,7] candidates=[2,3,6,7]中,因此 [ 7 ] [7] [7]是满足条件的一个解。由于数组有4个元素,用 7 7 7减去每个元素可以得到一个新 t a r g e t target target集合 t a r g e t n e w = { 5 , 4 , 1 , 0 } {target_{new}} =\{5,4,1,0\} targetnew={ 5,4,1,0},依次求出满足 t a r g e t n e w = { 5 , 4 , 1 , 0 } {target_{new}} =\{5,4,1,0\} targetnew={ 5,4,1,0}的解,加上上一次减去的数组中的元素,可以得到满足条件的新解。同时观察到 c a n d i d a t e s candidates candidates中最小元素不小于 2 2 2,因此 t a r g e t n e w = { 1 , 0 } {target_{new}} =\{1,0\} targetnew={ 1,0}的解是不存在的。通过这种判断可以减少循环数量。
实现代码如下:
vector<vector<int>> combinationSumSort(vector<int>& candidates, int target) {
vector<vector<int>> result;
size_t size = candidates.size();
if (0 == size)
return result;
if (target < candidates[0])
return result;
if (find(candidates.begin(), candidates.end(), target) != candidates.end())
{
vector<int> buf(1, target);
result.push_back(buf);
}
for (int i = 0; i < size; ++i)
{
int subTarget = target - candidates[i];
if (subTarget >= candidates[0])
{
vector<vector<int>> lastResult = combinationSum(candidates, subTarget);
for (auto& buffer : lastResult)
{
buffer.push_back(candidates[i]);
sort(buffer.begin(), buffer.end());
if (find(result.begin(), result.end(), buffer) == result.end())
result.push_back(buffer);
}
}
}
return result;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
return combinationSumSort(candidates, target);
}
需要注意的是:由于需要将 t a r g e t target target与数组中第一个元素判断大小,进行快速判断是否存在满足当前 t a r g e t target target的解。因此需要先对数组 c a n d i d a t e s candidates candidates进行排序。
递归求解时,存在很多重复计算。而动态规划可以减少重复计算。利用动态规划实现这个问题求解的思路如下:
已知最终求解数组满足 t a r g e t = 7 target=7 target=7的解,我们可以依次求出满足 t a r g e t = { 1 , 2 , 3 , 4 , 5 , 6 , 7