算法学习 —— 动态规划练习(一)
一、买卖股票的最佳时机(LeetCode-121)
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。
1.2 解题思路
想要利润最大,尽可能地要保证购买的时候价格最低,总利润最高。由此可以列出状态转移方程。
buy = min{buy,price}
profilt = max{profilt,price-buy}
1.3 解法
public int maxProfit(int[] prices) {
int profilt = 0;
int buy = Integer.MAX_VALUE;
for (int price : prices) {
buy = Math.min(buy, price);
profilt = Math.max(profilt, price - buy);
}
return profilt;
}
二、使用最小花费爬楼梯(LeetCode-746)
2.1 题目介绍
数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 costi。
每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。
您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
示例 1:
输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。
示例 2:
输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出: 6
解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。
注意:
cost 的长度将会在 [2, 1000]。
每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]。
2.2 解题思路
以样本为例cost = [10, 15, 20]
n = 3;
最后一步:
根据题意我们知道,最后一步不一定是cost[2](即20),还有可能是之后的值,我们这里假设有cost[3]且cost[3] = 0,这里我们就统一认为最后一步是cost[3],因为它的需要用的体力值设置为0。所以最后走到cost[2]所用的总体力值,和走到cost[3]的是一样的。
子问题
最后一步消耗的体力值是cost[3],总共消耗的体力值为f[3]。
走到了最后一步之后,往前回退,所面对的选择是,要么是倒退一步,要么是倒退两步。
于是有了子问题方程:
f[3] = min(f[1],f[2])+cost[3]
状态转移方程
f[x] = min(f[x-1],f[x-2])+cost[x]
初始条件
第一步可以从cost[0]开始走,也可以从cost[1]开始走。于是有了
f[0] = cost[0]
f[1] = cost[1]
边界
int costLength = cost.length;
cost[costLengh+1] = 0
f[costLengh+1]为最终的解。
2.3 解法
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
int[] f = new int[n+1];
//初始化
f[0] = cost[0];
f[1] = cost[1];
for (int i = 2; i <= n; i++) {
int tempCost = 0;
if (i == n) {
//cost[n] = 0
tempCost = 0;
} else {
tempCost = cost[i];
}
f[i] = Math.min(f[i - 1], f[i - 2]) + tempCost;
}
return f[n];
}
三、爬楼梯(LeetCode-70)
3.1 题目介绍
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
3.2 解题思路
最后一步:
最后一步是第n个台阶,我们这边假设为4。
子问题
最后一步到第4个台阶,假设总共有走法f[4]。
如果是走到第3个台阶的话,就只能再走1步
如果走到第2个台阶的话,可以走2步,或者走2次,分别走1步。
于是有了子问题方程:
f[4] = f[4-step1] + f[4-step2]
f[4] = f[3] + f[2]
状态转移方程
f[x] = f[x-1] + f[x-2]
3.3 解法
class Solution {
public int climbStairs(int n) {
int[] f = new int[n+1];
if(n<=2){
return n;
}
//初始化
f[0] = 0;
f[1] = 1;
f[2] = 2;
//f[x] = f[x-1] + f[x-2]
for(int i=3;i<=n;i++){
f[i] = f[i-1]+f[i-2];
}
return f[n];
}
}
四、最大子序和(LeetCode-53)
4.1 题目介绍
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
4.2 解题思路
分析能不能使用DP,要考虑符不符合DP的子问题重叠,即一个子问题,依赖于上一个子问题
简化输入,只有-2,则dp[0] 为最大子序列和,dp[0] = -2,
假设只有,-2,1,则dp[1]会参考dp[0],会比较1与-2+1的大小,从而确定要不要前面的-2,由此可得出
$dp[i] = \max\{ dp[i-1]+nums[i],nums[i]\} $
4.3 解法
public static int maxSubArray(int[] nums) {
// dp[i] = max(dp[i-1]+nums[i],nums[i])
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res = dp[0];
for (int i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
res = res > dp[i] ? res : dp[i];
}
return res;
}
参考文档
关于作者
后端程序员,五年开发经验,从事互联网金融方向。技术公众号「清泉白石」。如果您在阅读文章时有什么疑问或者发现文章的错误,欢迎在公众号里给我留言。