力扣题部分:
122.买卖股票的最佳时机II
题目链接:. - 力扣(LeetCode)
题面:
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
思路:
首先明确两个条件限制:
- 只有一只股票!
- 当前只有买股票或者卖股票的操作
为了利益最大化,显然我们应该在便宜的时候买进,在贵的时候卖出。
不难想出下面的操作原理:
通过读取当天和当天后一天的价格,比较两者大小和是否持有股票两个方面判断是否进行买卖操作。
如果差值大于零(当天便宜次天贵)且没有持有股票,购入股票。
如果差值小于零(当天贵次天便宜)且持有股票,卖出股票。
当然,因为最后一天没有后一天,所以放在for循环外单独操作。
代码实现:
class Solution {
public:
int maxProfit(vector<int>& prices) {
bool judge = false;
int i, money = 0, curprofit, profit = 0, preprice;
for(i = 0; i + 1 < prices.size(); i ++)
{
curprofit = prices[i + 1] - prices[i];
if(curprofit > 0 && !judge)
{
preprice = prices[i];
judge = true;
}
else if(curprofit < 0 && judge)
{
profit += prices[i] - preprice;
judge = false;
}
}
if(judge) profit += prices[i] - preprice;
return profit;
}
};
55. 跳跃游戏
题目链接:. - 力扣(LeetCode)
题面:
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
思路:
刚看到本题一开始可能想:当前位置元素如果是 3,我究竟是跳一步呢,还是两步呢,还是三步呢,究竟跳几步才是最优呢?
其实跳几步无所谓,关键在于可跳的覆盖范围!
不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。
这个范围内,别管是怎么跳的,反正一定可以跳过来。
那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!
每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点。
局部最优推出全局最优,找不出反例,试试贪心!
i 每次移动只能在 cover 的范围内移动,每移动一个元素,cover 得到该元素数值(新的覆盖范围)的补充,让 i 继续移动下去。
而 cover 每次只取 max(该元素数值补充后的范围, cover 本身范围)。
如果 cover 大于等于了终点下标,直接 return true 就可以了。
代码实现:
class Solution {
public:
bool canJump(vector<int>& nums) {
int i = 0, cover = nums[i];
if(nums.size() == 1) return true;
for(i = 0; i <= cover; i ++)
{
cover = max(i + nums[i], cover);
if(cover >= nums.size() - 1) return true;
}
return false;
}
};
45.跳跃游戏II
题目链接:. - 力扣(LeetCode)
题面:
给定一个长度为 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]
。
思路:
本题相对于上题还是难了不少。
但思路是相似的,还是要看最大覆盖范围。
本题要计算最少步数,那么就要想清楚什么时候步数才一定要加一呢?
局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。
整体最优:一步尽可能多走,从而达到最少步数。
思路虽然是这样,但在写代码的时候还不能真的能跳多远就跳多远,那样就不知道下一步最远能跳到哪里了。
所以真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!
这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖。
如果移动下标达到了当前这一步的最大覆盖最远距离了,还没有到终点的话,那么就必须再走一步来增加覆盖范围,直到覆盖范围覆盖了终点。
图中覆盖范围的意义在于,只要红色的区域,最多两步一定可以到!(不用管具体怎么跳,反正一定可以跳到)
移动下标达到了当前覆盖的最远距离下标时,步数就要加一,来增加覆盖距离。最后的步数就是最少步数。
这里还是有个特殊情况需要考虑,当移动下标达到了当前覆盖的最远距离下标时
- 如果当前覆盖最远距离下标不是是集合终点,步数就加一,还需要继续走。
- 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。
代码实现:
class Solution {
public:
int jump(vector<int>& nums) {
if (nums.size() == 1) return 0;
int curDistance = 0; // 当前覆盖最远距离下标
int ans = 0; // 记录走的最大步数
int nextDistance = 0; // 下一步覆盖最远距离下标
for (int i = 0; i < nums.size(); i++) {
nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖最远距离下标
if (i == curDistance) { // 遇到当前覆盖最远距离下标
ans++; // 需要走下一步
curDistance = nextDistance; // 更新当前覆盖最远距离下标(相当于加油了)
if (nextDistance >= nums.size() - 1) break; // 当前覆盖最远距到达集合终点,不用做ans++操作了,直接结束
}
}
return ans;
}
};
1005.K次取反后最大化的数组和
题目链接:. - 力扣(LeetCode)
题面:
给你一个整数数组 nums
和一个整数 k
,按以下方法修改该数组:
- 选择某个下标
i
并将nums[i]
替换为-nums[i]
。
重复这个过程恰好 k
次。可以多次选择同一个下标 i
。
以这种方式修改数组后,返回数组 可能的最大和 。
思路:
本题思路其实比较好想了,如何可以让数组和最大呢?
局部最优:让绝对值大的负数变为正数,当前数值达到最大,
整体最优:整个数组和达到最大。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让数组和达到最大?
局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了)
全局最优:整个 数组和 达到最大。
通过两个贪心的思路,不难分出下面几步并写出代码。
第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
第二步:从前向后遍历,遇到负数将其变为正数,同时K--
第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
第四步:求和
代码实现:
class Solution {
public:
static bool cmp(int a, int b)
{
return abs(a) < abs(b);
}
int largestSumAfterKNegations(vector<int>& nums, int k) {
sort(nums.begin(), nums.end(), cmp);
int sum = 0, i;
for(i = nums.size() - 1; i > 0; i --)
{
if(nums[i] > 0 || k <= 0) sum += nums[i];
else if(nums[i] < 0 && k > 0) sum -= nums[i], k --;
}
if(k % 2 == 1) nums[0] = nums[0] * -1;
sum += nums[0];
return sum;
}
};