动态规划
动态规划(dynamic programing)应用于子问题重叠的情况, 即不同的子问题具有公共的子子问题
通常按如下四步设计动规算法:
- 刻画一个最优解的结构特征
- 递归地定义最优解的值
- 计算最优解的值, 通常采用自底向上法
- 利用计算出的信息构造最优解
钢条切割
假设有一长度为i钢条, 销售价格表如下
长度 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格 | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
求利润最高的切割方式
过程分析
长度为n的钢条一共有2^(n- 1)种切割方式, 如果最优解将钢条切割为k段
那么最优切割方案
n = i1 + i2 + … + ik
带来的收益为
rn = pi1 + pi2 + … + pik
接下来观察一些最优方案
r1 = 1, 切割方案1 =1
r2 = 5, 切割方案 2= 2
r3 = 8, 切割方案 3 = 3
r4 = 10 , 切割方案 4 = 2 + 2
r5 = 13 切割方案 5 = 2 + 3
r6 = 17 切割方案 6 = 6
r7 = 18 切割方案 7 = 1 + 6或 7 = 2 + 2 + 3
r8 = 22 切割方案 8 = 2 + 6
可以得出, 对于rn(n >= 1)
rn = max(pn, r1 + rn-1, r2 + rn-2, …, rn-1 + r1)
那么就有直接的自顶向下的递归方法
CUT_ROD(p, n)
if (n == 0)
return 0;
q = INT_MIN;
for i = 1 to n
q = max(q, p[i] + CUT_ROD(p, n - i))
但是这么做效率是非常低的, 因为它反复求解了相同的子问题
dp思想
第一种方法是带备忘的自顶向下方法, 定义一个数组, 存放子问题的解, 每一次递归先去检测数组中是否存在子问题解
第二种是自底向上法, 将子问题由小到大求解。
自顶向下法并没有真正递归地考察所有可能子问题, 然而由于没有频繁递归调用的开销, 自底向上法有更小的时间复杂度函数系数
自顶向下法如下:
MEMOIZED-CUT_ROD(p, n)
let r be a new array
for(i = 0 to n)
r[i] = INT_MIN;
return MEMOIZED-CUT-ROD-AUX(p, n, r);
MEMORIZED-CUT-ROD-AUX(p, n, r)
if(r[n] >= 0)
return r[n];
if n == 0
q = 0;
else q = INT_MIN
for(i = 1 to n)
q = max(q, p[i] + MEMORIZED-CUT-ROD-AUX(p, n - i, r))
r[n] = q
return q
自底向上方法更加简单
BOTTON-UP-CUT-ROD(p, n)
let r be a new array
r[0] = 0
for(j = 1 to n)
q = INT_MIN
for(i = 1 to j)
q = max(q, p[i] + r[j - i])
r[j] = q
return r[n]
最长递增子序列
话不多说, 直接上代码
class Solution {
public int lengthOfLIS(int[] nums)
{
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
int res = 0;
Arrays.fill(dp, 1);
for(int i = 0; i < nums.length; i++)
{
for(int j = 0; j < i; j++)
{
if(nums[j] < nums[i])
dp[i] = Math.max(dp[i], dp[j] + 1);
}
res = Math.max(res, dp[i]);
}
return res;
}
}
解码
int numDecodings(char* s)
{
int dp[1000] = { 1, 0 };
int num1;
int num2;
int b = 0;
int i = 1;
if (s[b] == '\0')
return 0;
while (s[b] != '\0')
{
num1 = s[b] - '0';
if(i > 1)
num2 = ((s[b- 1] - '0') * 10 + num1);
else
num2 = 0;
if (num1 >= 1 && num1 <= 9)
dp[i] = dp[i - 1];
if (num2 >= 10 && num2 <= 26)
dp[i] += dp[i - 2];
i++;
b++;
}
return dp[--i];
}
组合总和
int combinationSum4(int* nums, int numsSize, int target)
{
int* dp = (int*)calloc(target + 1, sizeof(int));
dp[0] = 1;
for (int i = 1; i <= target; i++)
{
for (int j = 0; j < numsSize; j++)
{
if (nums[j] <= i &&dp[i] < 2147483647 - dp[i - nums[j]])
{
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}