动态规划由简及繁(三)
题目
给定一个数组,它的第 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。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
著作权归领扣网络所有。
分析
-
问题分解
要求从0…K的有序的最大差值,那么如果0…k-1能给我提供一个值,让我能根据这个值算出来最大值,就好了。
那么如果提供截止到k-1的最大值,显然不能帮助计算到k的最大值。
如果提供截止到k-1的最小数,再使用一个辅助变量max,就可以计算到k的最大值了。就是max(max, k - (min0…k-1))
-
状态
设置dp[i]为截止到i时,当前最小的值。
-
状态转移方程
nums[i], i = 0 min(nums[i], dp[i - 1]), i >= 1
-
编码
class Solution { public int maxProfit(int[] p) { if (p.length == 0) { return 0; } int[] dp = new int[p.length]; int max = 0; dp[0] = p[0]; for (int i = 1; i < p.length; i++) { dp[i] = p[i] < dp[i - 1]? p[i]: dp[i - 1]; max = Math.max(max, p[i] - dp[i - 1]); } return max; } }
-
也可以用dp[i]记录截止到i的最大值,那么就需要额外维护一个最小值,其实是一样的。
状态转移
nums[0], i = 0 dp[i] = max(dp[i - 1], nums[i] - minValue), i >= 1
编码
class Solution { public int maxProfit(int[] p) { if (p.length == 0) { return 0; } int[] dp = new int[p.length]; int minVal = p[0]; dp[0] = 0; for (int i = 1; i < p.length; i++) { if (p[i] < minVal) { minVal = p[i]; } dp[i] = Math.max(dp[i - 1], p[i] - minVal); } return dp[p.length - 1]; } }
-
优化
其实发现只需要保存截止到上一个的最小值,以及有序差异的最大值即可。
class Solution { public int maxProfit(int[] p) { if (p.length == 0) { return 0; } int minVal = p[0]; int max = 0; for (int i = 1; i < p.length; i++) { if (p[i] < minVal) { minVal = p[i]; } max = Math.max(max, p[i] - minVal); } return max; } }
题目
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-medium/xvb8zs/
来源:力扣(LeetCode)
分析
-
问题分解
如果要求能否到达最后一步,那么我们如果知道前一步所能跳的格子数,那么我们就知道能否到达最后一步了。
相当于把当前问题分解为了子问题
-
状态
设置dp[i]表示到达i时还能跳的最大格子数。
-
状态转移方程
如果上一个格子还能跳的格子数大于当前,那么当前格子还能跳的最大格子数就为之前的格子数减一,表示跳到了当前格子。否则那就是在当前格子所能跳的步数更远,选择当前格子。
如果上一个格子数为0,表示根本到不了上一个格子,那么之后所能到达的格子数全部为0
dp[0] = nums[0] dp[i] = dp[i - 1] > nums[i]? dp[i - 1] - 1: dp[i - 1] == 0? 0: nums[i];
-
边界条件
如果只有一格,那么直接就在终点。直接返回就行。
-
编码
写代码可以直接把上一格到达不了的情况直接返回false即可。
而且,我们无需计算最后一步,因为最后一步就是我们需要到达的目标点。直接返回倒数第一个格子能走的数是否大于0即可
class Solution { public boolean canJump(int[] nums) { if (nums.length <= 1) { return true; } int[] dp = new int[nums.length]; dp[0] = nums[0]; for (int i = 1; i < nums.length - 1; i++) { if (dp[i - 1] == 0) { return false; } dp[i] = dp[i - 1] > nums[i] ? dp[i - 1] - 1: nums[i]; } return dp[nums.length - 2] > 0; } }
-
优化
因为只需要记录上一个所能走的最大步数即可,从O(n)变成O(1)。
class Solution { public boolean canJump(int[] nums) { if (nums.length <= 1) { return true; } int lastMaxStep = nums[0]; for (int i = 1; i < nums.length - 1; i++) { if (lastMaxStep == 0) { return false; } lastMaxStep = lastMaxStep > nums[i] ? lastMaxStep - 1: nums[i]; } return lastMaxStep > 0; } }