1、 动态规划问题的描述:
动态规划问题与计算最优值问题有关,题目中问题的解法是不确定的,但是问题的解法却有一定的规则,每一步最优解之间必然有一定的联系。如果能够保存已解决子问题的答案,在后面的计算中用到这些答案,就可以很快解决问题。
2、动态规划问题的解法——填表法
为了将已解决的子问题答案保存起来,可使用一个表来记录所有已解决的子问题的答案,不管孩子问题以后是否被用到,只要它被计算过,就将结果填入表中。具体步骤如下:
(1)从子节点最简单的情况下计算、分析最优值,找到最优解的性质,并刻画其结构特征;
(2)递归的定义最优值,保存在表中;
(3)以自底向上的方式计算出最优值。
3、典型的动态规划问题
leetcode中提供了4道典型的动态规划问题:爬楼梯、买卖股票的最佳时机、最大子序和、打家劫舍,下面对每道题进行分析,找到最优解的递推公式。
- 爬楼梯
(1)问题描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你 可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?给定 n 是一个正整数。
示例1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
(2)分析:
问题是n层高的楼梯,最多有几种不同的方法可以爬到楼顶。我们要关注的是最优解之间的联系,而不是最优解的构成,因此用一个表记录6层高的楼梯的最优解的情况。
楼层高度 | 1 | 2 | 3 | 4 | 5 | 6 | …… |
---|---|---|---|---|---|---|---|
解决的方法总数 | 1 | 2 | 3 | 5 | 8 | 13 | …… |
从表中不难看出,当n=1时,results[n]=1;当n=2时,results[n]=2;当n>=3时,与前两个子问题的解有关,results[n]=results[n-1]+results[n-2]。
(3)解答
int climbStairs(int n) {
int result[1000000];
result[0] = 1;
result[1] = 2;
if(n<3)
return n;
for(int i=2; i<n; i++){
result[i]=result[i-1]+result[i-2];
}
return result[n-1];
}
- 买卖股票的最佳时机
(1)问题描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。
示例1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
(2)分析:
计算最大利润:
第一天只能买进没法卖出,所以第一天的最大利润为maxProfit[1]=0;
第二天的股票价格如果大于第一天的股票价格,那么第二天的最大利润为prices[2]-prices[1],如果第二天的股票价格小于第一天的,那第二天的最大利润为maxProfit[2]=0
或者说maxProfit[2] = max(prices[2]-prices[1],maxProfit[1])
第三天以后,maxProfit[n] = max(prices[n]-minPrice,maxProfit[n-1]),minPrice是天数小于n的最小的股票价格
(3)解答
int maxProfit(int* prices, int pricesSize) {
int minPrice=prices[0],i;
int maxProfitValue[100000];
if(pricesSize==0)
return 0;
maxProfitValue[0] = 0;
for(i=1; i< pricesSize; i++){
if(prices[i]-minPrice>0)
maxProfitValue[i] = prices[i]-minPrice>maxProfitValue[i-1]?prices[i]-minPrice:maxProfitValue[i-1];
else{
maxProfitValue[i] = maxProfitValue[i-1];
minPrice = prices[i];
}
}
return maxProfitValue[pricesSize-1];
}
- 最大子序和
(1)问题描述:
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
(2)分析:
当数组中只有一个元素时,最大和为该元素的值MaxSum[1] = A[1];
当数组中新增加一个元素,如果之前的最大和大于0,则可以继续累加,如果之前的最大值小于0,就以新加的元素重新开始,即MaxSum[i] = Max{ MaxSum[i-1] + A[i], A[i]}
(3)解答
int maxSubArray(int* nums, int numsSize) {
int current = nums[0];
int sum = nums[0];
for(int i = 1; i<numsSize; i++){
if(current<0)
current = nums[i];
else
current += nums[i];
if(current>sum)
sum = current;
}
return sum;
}
- 打家劫舍
(1)问题描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
(2)分析:
如果选择抢劫上一个屋子,则不能抢劫当前的屋子。这样,最大的收益便是抢劫上一个屋子的收益。
如果选择抢劫当前的屋子,则不能抢劫上一个屋子。这样,最大的收益便是当前屋子的收益加上上一个屋子的上一个屋子的收益。
因此,当前的最大收益=max(上一次收益,上上次收益+当前屋子的收益)
(3)解答
int rob(int* nums, int numsSize) {
int i;
if (numsSize == 0)
return 0;
if (numsSize == 1)
return nums[0];
int result[100000];
result[0] = nums[0];
result[1] = (nums[0]>nums[1]) ? nums[0]:nums[1];
for(i = 2; i<numsSize; i++) {
result[i] = (nums[i]+result[i-2])>result[i-1] ? nums[i]+result[i-2]:result[i-1];
}
return result[numsSize-1];
}