动态规划是一个比较牛掰的算法,它与分治法相似。分治法是从大问题分解成小问题,自顶而下求解,再合并得到原问题的解;动态规划规划也是大问题分解成小问题,但是这些问题之间存在着某种联系,类似于数学归纳,自底向上求解,先求解最小的子问题,把结果存储在表格中,再求解大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高算法的效率。
动态规划的解题步骤一般分为4步:
- 分解最优解的结构特征。
- 建立最优值得递归式。
- 自底向上计算最优解,并记录。
- 构造最优解。
那什么样的问题可以已使用动态规划算法呢?只需要具备以下的两个性质:
- 最优子结构。
- 子问题重叠。
例题:石子合并
有n堆石子堆放在路边,现要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费(最大或最小)。
eg:我们以最小为例([5,8,6,9,2,3]):
1.分解最优解的结构特征。
假设我们知道了在第k堆的石子分开可以获得最优解,那么原问题就变成了2个子问题a1,a2。只需要证明原问题的最优解包含两个子问题的最优解。
假设n对石子合并起来的最优花费是c,子问题a1的最优解是m,子问题a2的最优解是n,两个子问题的石子数量之和是w(i,j),那么 c=m+n+w(i,j) 。因此只需要证明c是最优的,则a和b一定是最优的。
证明: 如果m不是最优的,那子问题a1一定存在最优解m’,m’<m,那么m’+n+w(i,j) < c,这与我们的假设c是最优解相矛盾,因此c一定是最优的,则m也是最优的。同理可证n也是最优的。
所以原问题的最优解包含了子问题的最优解。
2.建立最优值得递归式。
Min[i][j]表示从第i堆石子到底j堆石子合并的最小花费。
Min[i][j] = { 0 (i=j)
min(Min[i][k] + Min[k+1][j] + sum[j] - sum[i-1]) (i<j)
Min[i][j] | t条件 |
---|---|
0 | i=j |
min(Min[i][k]+Min[k+1][j]+sum[j]-sum[i-1]) ,i<=k<j | i<j |
3.自底向上计算最优解,并记录。
0 | 13 | 32 | 56 | 71 | 84 | |
0 | 14 | 37 | 50 | 61 | ||
0 | 15 | 28 | 39 | |||
0 | 11 | 19 | ||||
0 | 5 | |||||
0 |
4.构造最优解。
0 | 13 | 32 | 56 | 71 | 84 | |
0 | 14 | 37 | 50 | 61 | ||
0 | 15 | 28 | 39 | |||
0 | 11 | 19 | ||||
0 | 5 | |||||
0 |
//合并石子
function mergeStones(arr){
//石子的总数
let sum = new Array();
sum[0] = 0;
for(let i = 1; i <= arr.length; i++){
sum[i] = sum [i-1] + arr[i-1];
}
//二维数组存储最大,最小值
let Min = new Array(arr.length+1);
let Max = new Array(arr.length+1);
for(let i = 0; i <= arr.length; i++){
Min[i] = new Array(arr.length+1);
Max[i] = new Array(arr.length+1);
}
//i=j
for(let i = 1; i <= arr.length; i++){
Min[i][i] = 0;
Max[i][i] = 0;
}
//i<j
for(let len = 2; len <= arr.length; len++ ){
for(let i = 1; i<= arr.length+1-len; i++){
Max[i][i+len-1] = Max[i][i] + Max[i+1][i+len-1] + sum[i+len-1] - sum[i-1];
Min[i][i+len-1] = Min[i][i] + Min[i+1][i+len-1] + sum[i+len-1] - sum[i-1];
for(let k = i+1; k < i+len-1; k++){
Max[i][i+len-1] = Math.max(Max[i][i+len-1] , Max[i][k] + Max[k+1][i+len-1] + sum[i+len-1] - sum[i-1]);
Min[i][i+len-1] = Math.min(Min[i][i+len-1] , Min[i][k] + Min[k+1][i+len-1] + sum[i+len-1] - sum[i-1]);
}
}
}
console.log('最大:'+Max[1][arr.length])
console.log('最小:'+Min[1][arr.length])
}
mergeStones([5,8,6,9,2,3])
//最大:129
//最小:84