经分解得到的子问题有时候往往不是互相独立的。不同子问题的数目常常只有多项式量级。在用分治法求解时,有些子问题被重复计算了许多次。
Those who cannot remember the pastare doomed to repeat it. ——George Santayana
(不取前车之鉴,必将重蹈覆辙。)
1.基本思想
如果分解得到的子问题不是互相独立的,此时若能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而提高计算效率。
2.解题步骤
I.找出最优解的性质,并刻划其结构特征。(最优子结构)II.递归地定义最优值。III.以自底向上的方式计算出最优值。IV.根据计算最优值时得到的信息,构造最优解。
3.动态规划的应用例子
(a) 最大连续乘积子数组
I.问题描述:任意取出数组中的若干个连续的数相乘,找出其中乘积最大的子数组II.解题思路:考虑到负数的存在,即负负得正的情况可能成为乘积最大的子数组,故需要同时并记录找出最大乘积和最小乘积III.递归结构:maxEnd = max(max(maxEnd * a[i], minEnd * a[i]), a[i]);minEnd = min(min(maxEnd * a[i], minEnd * a[i]), a[i]);IV.一层for循环,时间复杂度为O(n)
public double maxProductSubstring(double[] a){
double maxEnd = a[0];
double minEnd = a[0];
double maxResult = a[0];
for (int i = 1; i < a.length; i++) {
double end1 = maxEnd * a[i], end2 = minEnd * a[i];
maxEnd = Math.max(Math.max(end1, end2), a[i]);
minEnd = Math.min(Math.min(end1, end2), a[i]);
maxResult = Math.max(maxEnd, maxResult);
}
return maxResult;
}
(b) 0-1背包问题
I.问题描述:一共有n件物品,对每一件物品i都有两种选择,即装入背包或不装入。一件物品只能装一次且不能装入部分物品II.递归结构:
public static int package0_1(int[] v, int[] w,int Max){
int len = v.length;
int[] dp = new int[len];
for (int i = 0; i < len; i++) {
for (int j = Max; j >= w[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
}
}
return dp[len];
}
(c) 最长公共子序列(LCS)
I.问题描述:给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列,求X、Y的最长公共子序列II.最优子结构:
III.递归结构:
IV.对于m*n个子问题均需要计算一次,故时间复杂度为O(m*n)V.递归结构中空间复杂度为O(m*n),可优化为O(min(m,n))
public static int lcsLength(char[] x, char[] y){
int m = x.length;
int n = y.length;
int k = m > n ? n : m;
int[] c = new int[k+1];
int leftabove, above;
for (int i = 1; i <= m; i++) {
leftabove = c[0];
above = c[1];
for (int j = 1; j <= n; j++) {
if(x[i-1] == y[j-1]){
c[j] = leftabove + 1;
}
else{
c[j] = Math.max(c[j], c[j-1]);
}
//更新变量
leftabove = above;
above = c[(j+1) % n];
}
}
return c[k];
}