文章目录
Day 32
01. 买卖股票的最佳时机 II(No. 122)
1.1 题目
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例 1:
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
总利润为 4 。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
提示:
1 <= prices.length <= 3 * 104
0 <= prices[i] <= 104
1.2 笔记
初次见到这个题的时候大家想的可能是选择价格最低的买入再选择价格最高的卖出,但显然是不合理的,因为可以有 多次买入和卖出 来收取多次的利润。
但其实利润是可以分解的,先选择价格低的时候买入,价格高的时候直接卖出。
两天之间其实只有三种情况:
- 后一天比前一天贵
- 后一天比前一天便宜
- 后一天和前一天一样多
本题是可以在同一天去卖出的,所以采取这样的贪心策略:
- 如果后一天比前一天便宜那就将 前一天的卖出 再去买后一天的
- 如果后一天比前一天贵,那就直接卖出来收取利息,并且 买入这一天的股票
- 后一天和前一天一样多的话不做处理即可
其实取得的就是所有的正数和,举不出其他的反例,来试试贪心算法
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[prices.length];
int res = 0;
int pre = prices[0]; // 记录昨天的金钱
for (int i = 1; i < prices.length; i++) {
if (prices[i] > pre) {
// 说明今天的股价升高了 卖出并且将今天的买入
res += prices[i] - pre;
pre = prices[i];
} else {
pre = prices[i];
}
}
return res;
}
}
可以发现 if
和 else
都有 pre = prives[i]
的语句,提取到外面,这是因为无论何种情况,都买入了今天的股票。
1.3 代码
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[prices.length];
int res = 0;
int pre = prices[0]; // 记录昨天的金钱
for (int i = 1; i < prices.length; i++) {
if (prices[i] > pre) {
// 说明今天的股价升高了 卖出并且将今天的买入
res += prices[i] - pre;
}
pre = prices[i];
}
return res;
}
}
02. 跳跃游戏(No. 55)
2.1 题目
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
提示:
-
1 <= nums.length <= 104
-
0 <= nums[i] <= 105
2.2 笔记
遇到这个题的时候,纠结的就是我在这个节点上跳几步呢?
其实完全不需要考虑,我们只需要考虑这个节点 最远 能到哪个位置就可以了,因为上一个节点能够到达这个节点,这个节点又能取到最远的这个节点,所以
节点可以到达这个范围内的任何位置
只需要判断这个最远举例能否延伸到最后一个节点即可。
比如上面的案例 1
先根据节点 0
可以到达节点 1
和节点 2
,通过节点 1
又可以最远到达节点 4
,这时候就可以返回 true
。
再来看另一个案例
通过节点 0
可以到达节点 3
,所以在 0 - 3
的范围内去寻找另一个最远的距离,发现直到遍历到 3
也无法到达节点 4
,出循环,直接返回 false
。
所以能得到如下的贪心策略:
- 先找第一个节点能到达的最远距离,遍历这个范围看第二次最远能到达哪里,重复这个步骤,直到这个最远能够到达最后的位置。
- 如果遍历完整个范围还是没能扩展这个范围,说明被困死在这个范围中了,必然无法到达目标节点。
先定义一个 变量 cover
来存储,从起始节点能到达的最远距离
int cover = 0;
然后去循环整个数组,直到到达最终的节点或者发现其 被困死
// 遍历从 i 到 cover 这个范围
for (int i = 0; i <= cover; i++) {
cover = Math.max(i + nums[i], cover);
if (cover >= nums.length - 1) {
return true;
}
}
return false;
2.3 代码
class Solution {
public boolean canJump(int[] nums) {
int cover = 0;
if (nums.length <= 1) {
return true;
}
// 遍历从 i 到 cover 这个范围
for (int i = 0; i <= cover; i++) {
cover = Math.max(i + nums[i], cover);
if (cover >= nums.length - 1) {
return true;
}
}
return false;
}
}
03. 跳跃游戏 II(No. 45)
3.1 题目
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
提示:
1 <= nums.length <= 104
0 <= nums[i] <= 1000
- 题目保证可以到达
nums[n-1]
3.2 笔记
这道题与上一道题的区别就是本题需要得到的是 最小跳跃 的次数
那在一个范围的节点中,什么时候去跳跃能够使得跳跃的次数最短呢?
答案就是从 跳的最远的那个节点 开始跳跃
比如上面这个例子,从节点 0
可以到达节点 1
和节点 2
,这两个节点都能够去 扩充 这个最大范围,但为了保证跳跃的次数最少,选择的节点就是移动的最远的那个节点,即节点 2
,所以当确定了一个范围后,要去寻找这个 最远的节点
因为要完全遍历完整个范围才能知道哪个是最远的,所以需要一个变量来存储 当前的距离,还需要一个变量来存储 最远的距离
int currentCover = 0; // 此时能到达的最远距离
int nextCover = 0; // 下次能到达的最远距离
int res = 0; // 结果
因为本题是保证可以跳到最终的节点的,所以遍历直接从 0
到 nums.length
即可
for (int i = 0; i <= nums.length; i++) {}
接下来要解决的就是什么时候去更新这个 res
了,上面说到是找到范围内最远距离的那个节点的时候,其
也就是 i
遍历到 currentCover
的时候,这时候就确定了最远的节点,然后将 nextCover
赋值给 currentCover
再进行下一次循环。
写出代码。
3.3 代码
class Solution {
public int jump(int[] nums) {
int currentCover = 0; // 此时能到达的最远距离
int nextCover = 0; // 下次能到达的最远距离
int res = 0; // 结果
if (nums.length <= 1) {
return 0;
}
for (int i = 0; i <= nums.length; i++) {
nextCover = Math.max(nums[i] + i, nextCover);
if (i == currentCover) { //
res++;
currentCover = nextCover;
if (currentCover >= nums.length - 1) {
break;
}
}
}
return res;
}
}