算法学习记录~2023.X.XX~章节DayX~题目号.题目标题 & 题目号.题目标题
122.买卖股票的最佳时机II
题目链接
思路1:贪心
将总利润细分为每两天之间那一整天的利润,只要是正的那就可以加入,否则就不操作。
局部最优:收集每天的正利润
全局最优:求得最大利润
因此根据价格序列构建一个利润序列(长度会比价格序列短一个,因为至少得有两天才有买卖交易可能性),有正的那就都是局部最优,然后累加即可
代码
//自己写的
class Solution {
public:
int maxProfit(vector<int>& prices) {
int sum = 0;
vector<int> difference(prices.size() - 1); //差值
for (int i = 0; i < difference.size(); i++){
difference[i] = prices[i + 1] - prices[i];
}
for (int i = 0; i < difference.size(); i++){
if (difference[i] > 0){
sum += difference[i];
}
}
return sum;
}
};
//carl哥写的
class Solution {
public:
int maxProfit(vector<int>& prices) {
int result = 0;
for (int i = 1; i < prices.size(); i++) {
result += max(prices[i] - prices[i - 1], 0);
}
return result;
}
};
思路2:动态规划
完成下一章节动态规划后再补
总结
55. 跳跃游戏
题目链接
思路
问题可以转化为跳跃覆盖范围究竟可不可以覆盖到终点,因为数组对应的值是最大跳跃长度,因此小于它的所有地方都一定可以到达。
从第一个点的值取得第一个范围,在该范围下的每一个点,根据取值都能获得新的范围,不断更新最大值。
通过不断更新范围和遍历该范围下所有点能到的最远距离,就能求出来从第一个点出发能到达的最远范围。
如果该范围大于数组长度,则说明一定能覆盖到。
局部最优解:每次取最大跳跃步数(取最大覆盖范围)
整体最优解:最后得到整体最大覆盖范围,看是否能到终点。
代码
class Solution {
public:
bool canJump(vector<int>& nums) {
int range = 0; //能到达的最大范围
if (nums.size() == 1) //一个数的话一定能达到
return true;
for (int i = 0; i <= range; i++){ //第一注意是range,第二注意要 <=
range = max(i + nums[i], range);
if (range >= nums.size() - 1) //range超过nums的大小则一定能覆盖到
return true;
}
return false;
}
};
总结
45.跳跃游戏II
题目链接
思路
同样是看覆盖范围。
想以最小步数到达终点,那就一步步进行计算,确保每一步都尽可能走远,一旦覆盖了终点,那就是最小步数。
使用一个 curRange 和 nextRange,分别记录当前最远范围和加一步的话的最远距离。从第一个点开始遍历,在curRange范围内的每个数都进行nextRange的更新,只取最大值。只有nextRange能够到达最后位置时才退出获得答案,否则就继续往后一步重复上述过程。
代码
class Solution {
public:
int jump(vector<int>& nums) {
if (nums.size() == 1)
return 0;
int curRange = 0 ; //初始最大距离
int nextRange = 0 + nums[0]; //初始下一步最大距离
int count = 0; //初始步数
for (int i = 0; i < nums.size(); i++){
nextRange = max(nextRange, i + nums[i]); //更新当前范围下每一步能走的最大距离
//由于使用了nextRange判断是否能到最后一步,因此只需不断更新当前能走范围内的最大距离
//当走到当前范围尽头时如果还没到达最远,则需要加一步,更新当前最大距离为nextRange
//如果nextRange能到达最后位置则已经找到答案
//否则继续遍历更新下一步范围内能到的最远位置,重复上述步骤
if (i == curRange){ //走到了当前范围内能走的最远位置
if (curRange < nums.size() - 1){ //到了当前最远距离但还是没走到最后,说明还需要后续步数
count ++; //再走一步
curRange = nextRange; //更新当前最大范围
if (nextRange >= nums.size() - 1) //下一步能到最后位置则直接退出
break;
}
}
}
return count;
}
};
总结
一开始考虑的是找出所有能走到最后一步的路径,之后再找出步数最少的一个。但后来发现这样非常复杂,而且暂时没想出来具体解决方案。
看了题解后才想到尽量使用贪心思路,改变思路考虑,一步一步地算,每一步考虑好最优解,最终就是全局最优解
1005. K 次取反后最大化的数组和
题目链接
思路1:比较差的自己想的懒方法
取最大值的话,那就每次修改都只修改最小的值
因此每次都先排序一下,然后把最小值取反,直到用完k
思路比较好想,但每次都排序一下显然会成本更高
代码
class Solution {
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
//取最大值的话,那就每次修改都只修改最小的值,因此每次都排序一下然后把最小值取反
while (k != 0){
k--; //统计次数
sort(nums.begin(), nums.end()); //排序
nums[0] = -nums[0]; //最小值取反
}
int sum = 0; //统计数组和
for (int i = 0; i < nums.size(); i++){
sum += nums[i];
}
return sum;
}
};
思路2:分情况进行贪心考虑
主要有两种情况:
- 如果有负数 —> 将绝对值更大的负数取反则总和一定更大
- 如果负数用完 / 全都是正数 —> 找数值最小的正整数进行取反则总和一定更大
因此具体的解决思路是:
- 将数组按照绝对值大小从大到小排序,必须是按照绝对值大小
- 从前向后遍历,遇到负数将其变为正数,同时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; //用于求和
for (int i = 0; i < nums.size(); i++){ //先将所有负数或0按绝对值从大到小进行取反
if (nums[i] <= 0 && k > 0){
nums[i] = -nums[i];
k --;
}
if (k == 0)
break;
}
//此时如果k还没用完则数组已经全变成了正数数组
while (k > 0){ //如果k还没用完就不断取反数值最小的一个
nums[nums.size() - 1] = -nums[nums.size() - 1];
k --;
}
for (int i = 0; i < nums.size(); i++) //数组求和
sum += nums[i];
return sum;
}
};
总结
按照贪心思想考虑事件划分为小事件后的所有情况,然后再找到解决方案,一方面能解决问题,一方面能尽可能提高效率。