从下面三道LeetCode的习题中分析简单的动态规划的思想
1.(LeetCode) 70.爬楼梯
题目描述:
假设你正在爬楼梯。需要 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 阶
题目分析:
这是一道基础的动态规划习题,也是在很多动态规划题目中很好理解的一个例子。
首先从题目的要求的结果出发,题目中要求爬到第n级台阶所需的方法。爬上楼梯的方法有两种,一种是一次爬一级台阶,一种是一次爬两级台阶。因此可以倒推出有两种方法可以到达最后一级台阶,即从n-1级楼梯迈一步上去,另一种是从n-2级楼梯迈两步上去。即 (到达n级楼梯的可能性)=(到达n-1级楼梯的可能性)+(到达n-2级楼梯的可能性)。然后问题就变成了求两个子问题的解,一步步倒推,倒推到已经解:即到达第一级楼梯只有一种方法,到达第二级楼梯有两种方法。从这两个已知的解就能求出问题最后的解。
程序没有采用递归的形式,因为递归栈占用的内存空间太大,且时间效率也很不理想。我们注意到求到达n级楼梯只需知道它的下两级楼梯所需的方法数。因此在程序中设置了两个变量保存这两个值(下面两个题目也有类似的思想)。
算法代码:
class Solution {
public int climbStairs(int n) {
if(n==1){ //楼梯只有一级时,方法数为1
return 1;
}
else if(n==2){ //楼梯只有二级时,方法数为2
return 2;
}
int a=1,b=2; //a表示前两级楼梯的方法,b表示前一级楼梯的方法
int temp=0;
for(int i=3;i<=n;i++){
temp=a+b; //第i级阶梯的方法数为两者相加
a=b; //更新a
b=temp; //更新b
}
return temp;
}
}
2.(LeetCode) 198.打家劫舍
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 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 。
题目分析:
因为不能盗窃相邻房屋内的物品,即不能将数组相邻的两个值相加。程序中设置了一个choose数组,choose[i]表示盗取前i个房屋的最大金额,choose[arr.length]即为盗取金额的最大值,要使盗取的金额最大,要么就放弃盗窃这家,保持与前一个房屋盗取的总金额,要么就是取前一个的前一个的房屋的金额加上要盗取的房屋的金额(因为两者不相邻),在这两个选择间取最大值。
动态规划的递推方程为:choose[i]=Math.max(choose[i-1],choose[i-2]+arr[i]);
算法代码:
class Solution {
public int rob(int[] nums) {
if(nums.length==0){ //没有房屋,金额为0
return 0;
}
if(nums.length==1){ //只有一个房屋,取第一个为最大金额
return nums[0];
}
if(nums.length==2){
return Math.max(nums[0],nums[1]); //两个房屋,取其中最大值
}
int[] choose=new int[nums.length]; //choose[i]为盗取前i个房屋时可以累积的最大金额
choose[0]=nums[0];
choose[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<nums.length;i++){
int a=choose[i-2]+nums[i]; //选择一,盗窃这家房屋,再加上前i-2房屋的总金额
int b=choose[i-1]; //选择二,不盗取进入的这个房屋,取相邻的房屋的最大值
choose[i]=Math.max(a,b); //在两个选择中取一个最大的
}
return choose[nums.length-1]; //choose数组的末尾就是遍历了整个数组的最大金额
}
}
3.(LeetCode) 121.买卖股票的最佳时机
题目描述:
给定一个数组,它的第 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。
题目分析:
这道题目不用动态规划的思想或许会更简单,但这里还是阐述一下动态规划来解决这道题目的思想
要使股票卖出最大价格,一个选择自然是在第n天不进行任何买进或者卖出的操作,此时的最大利润为前一天的最大利润。第二个选择是若当天的股票价格极高,可以选择在这个时候卖出,这时候的最大收益就和前一天的最大利润没有任何关系了,此时的最大利润为当天的价格-在当天之前的最低价格(理解为在这个价格最低的时候买进)。因此动态规划的递推方程为:
前i天的最大收益=Math.max(前i-1天的最大收益,当天的价格-前i天的最低价格);
算法代码:
class Solution {
public int maxProfit(int[] prices) {
if(prices == null || prices.length == 0) { //数组不存在或长度为0
return 0;
}
int profit = 0; //前i天的最大收益
int min = prices[0]; //min记录的是前i天的最低价格
for(int i = 1; i < prices.length; i++){
profit = Math.max(profit, prices[i] - min); //最大收益在两个选择间
if(min > prices[i]) {
min = prices[i]; //每次循环都更新min的值,确保min是前i天最小的值
}
}
return profit;
}
}