简单动规四道练习题
爬楼梯
题目:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶 示例 2:
示例2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
思路一:简单递归
首先,考虑特殊情况,即阶梯数为0,1,2时,分别返回值为0,1,2。
然后我们只需要返回n-1 和n-2即可,让他逐层往下递归。(爬楼梯每次只趴1或2,从总阶数倒过来看,总阶数的方法数由n-1阶数和n-2阶数的方法数来决定)
即:
public int climbStairs(int n) {
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 2;
return climbStairs(n-1)+climbStairs(n-2);
}
此方法性能上有很大的浪费,因为,在进行递归时,有很大一部分计算是重复了的,比如,在进行计算climbStairs(n-1) 时,它要递归计算climbStairs(n-2)+climbStairs(n-3);而在进行计算climbStairs(n-2)时,它其实已经被算过一次了,但是还要重复计算。
思路二:hashmap
由思路一知道,计算时,会有重复计算的情况,所以可以想办法把一些值存起来,以后就不需要再计算。
public int climbStairs(int n) {
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 2;
return climbStairs(n-1)+climbStairs(n-2);
Map<Integer,Integer> map= new HashMap<>();
return climbStairs(n,map);
}
public int climbStairs(int n,Map<Integer,Integer> map){
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 2;
if(map.containsKey(n)){
return map.get(n);
}else{
int val = climbStairs(n-1,map)+climbStairs(n-2,map);
map.put(n,val);
return val;
}
}
思路三:动规
每一个的阶数是前面俩的和F(n)=F(n-1)+F(n-2)
这不就是斐波那契数列。
这样可以从前往后推。
public int climbStairs(int n) {
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 2;
int a=1,b=2,temp=0;
for(int i=2;i<n;i++){
temp = a+b;
a=b;
b=temp;
}
return temp;
}
买卖股票最佳时机
题目:
给定一个数组,它的第 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。
思路一:暴力法
对于此题可以通过两重循环,依次比较更新最大的利润值。
思路二:保存最小值、最大利润
可以注意到,最大利润肯定是,最小值在左,最大在右。
那么我们只需要一次遍历,在这一次遍历中,要存储最小值,并维护一个最大利润值,当一遍遍历的时候,当前值-最小值>最大利润值时,替换最大利润
public int maxProfit(int[] prices) {
if(prices==null || prices.length==0){
return 0;
}
int profit=0, min=prices[0];
for(int i=0;i<prices.length;i++){
if(min>prices[i])
min = prices[i];
if(prices[i]-min>profit){
profit = prices[i]-min;
}
}
return profit;
}
最大子序和
思路一:暴力法
一位学长给我说过,如果刷题的时候一时间想不出来怎么做,可以先从最简单暴力法开始。
对于这个题,可以通过三层循环。
对于 [7,1,5,3,6,4]
- 第一层,从7遍历到4
- 第二次,从上层的那个数的下一个数开始,遍历到4
- 第三层,在一二层之间两个数进行遍历相加,并存储为max
最后返回max。
思路二:改进暴力
很明显,上面暴力法中第三层循环时多余的,因为在第二层遍历的时候,就可以存储最大值。
思路三:动规
参考文章:leetcode-最大子序和(四种)
在一遍遍历中,只要每次都保存着之前的最大值即可。
- 当临时最大值小于0,直接换成数组中的值
- 当前值大于0的话,就同nums[i]相加
设sum[i]为以第i个元素结尾且和最大的连续子数组。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i] = max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小
public int maxSubArray(int[] nums) {// 动态规划法
int sum=nums[0];
int n=nums[0];
for(int i=1;i<nums.length;i++) {
if(n>0)n+=nums[i];
else n=nums[i];
if(sum<n)sum=n;
}
return sum;
}https://blog.csdn.net/zwzsdy/article/details/80029796
打家劫舍
题目:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 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 。
这是一道典型的动态规划题,类似01揹包,对于一家屋子偷或不偷。
dp[i]=max(dp[i-1],dp[i-2]+nums[i])
解法
public int rob(int[] nums) {
if(nums.length==0 || nums==null){
return 0;
}
int[] results = new int[nums.length+1];
results[0] = 0;
results[1] = nums[0];
for(int i=2;i<=nums.length;i++){
results[i]=Math.max(results[i-2]+nums[i-1],results[i-1]);
}
return results[nums.length];
}