目录
LeetCode122.买卖股票的最佳时机II
给你一个整数数组
prices
,其中prices[i]
表示某支股票第i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
思路:一般来说,就是遇到最小值购入,最大值抛出,然后循环往复。
但是其实不用算整体,而是将每天的利润算出,看是正还是负。
这里有两种方法,贪心算法和动态规划。
1. 贪心算法
贪心算法就比较简单,局部最优就是说我将每天的利润计算出来后,只取正的利润;当我将所有的正利润统计完成后,即得到了全局最优。
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;
}
时间复杂度:O(n)
空间复杂度:O(1)
2. 动态规划
动态规划的话首先需要开辟一个辅助数组dp,dp[i][0]存放的是第i天的时候购入股票时所拥有得最大价值;dp[i][1]则是指第i天得时候不买股票所持有的最大价值,每一天都会有dp[i][0]和dp[i][1]两个值,循环的时候将每天的这两种情况不断更新,最后返回dp[prices.size()-1][0]和dp[prices.size()-1][1]中的最大值即可。
int maxProfit(vector<int>& prices) {
int size = prices.size();//记录prices的大小
vector<vector<int>> dp(size, vector<int>(2,0));//设置dp二维数组
//其中dp[i][0]表示第i天持有股票后的最大价值
//dp[i][1]表示第i天持有的最大价值
dp[0][0] -= prices[0];//计算第1天,下标为0的元素,持有股票后的价值
for(int i = 1; i < size; i ++){
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
//dp[i-1][1]-prices[i]表示前面一天没有买股票,用前一天的dp[i-1][1]来获得了第i只股票,所以要减
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
//dp[i-1][0]+prices[i]表示前一天买了股票,现在开始将股票卖了,所以要加
}
return max(dp[size - 1][0], dp[size - 1][1]);
}
时间复杂度:O(n)
空间复杂度:O(n)
LeetCode55.跳跃游戏
给你一个非负整数数组
nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标,如果可以,返回
true
;否则,返回false
。
思路:跳跃游戏其实关键就在于需要及时更新最大能到达的范围。
每走一步就需要尝试能够依据题目中的条件对下一次能到达的范围进行扩充,如果最后的覆盖范围大于等于了数组的最后元素,那么即可返回true,因为存在这样一条路,能够跳到数组末尾。
bool canJump(vector<int>& nums) {
int cover = 0;//记录最大覆盖范围
if(nums.size() == 1) return true;//单个元素一定能到达
for(int i = 0; i <= cover; i ++){
cover = max(i + nums[i], cover);//这里取的是两者的最大值,判断是否更新cover
if(cover >= nums.size() - 1) return true;
}
return false;
}
时间复杂度:O(n)
空间复杂度:O(1)
LeetCode45.跳跃游戏II
给定一个长度为
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]
。
思路:这和上面的题相比就有一点难度了。
因为要返回的是最少的步数,不是存不存在的问题了。
所以需要使用最少的步数,来达到最大的覆盖范围。
需要设置一个curIndex,标记当前步数能到达的范围,同时设置一个nextIndex标记下一步能够到达的下标索引。
这样的话,在循环里面就可以在当前步数的区间内,尝试尽可能大的扩展下一步的覆盖范围,而当我们再到达当前步的最大索引下标位置处时,就需要往前走一步了,更新curIndex,同时需要判断是否更新过的curIndex已经覆盖了最后位置,如果是,那就直接结束循环可以返回了。
其实我们没有必要在意每一步具体移动了几个单位,我们所找的就是在最小的步数范围内尽可能地实现最大地覆盖范围,只要当前索引下标在当前地步数范围区间内,那都是可取的,是我们尝试进一步扩展覆盖范围的一些试验元素。
int jump(vector<int>& nums) {
if(nums.size() == 1) return 0;
int curIndex = 0;//记录当前步所到达的下标位置
int nextIndex = 0;//记录下一步所到达的下标位置
int step = 0;//记录步数
for(int i = 0; i < nums.size(); i ++){
nextIndex = max(i + nums[i], nextIndex);//更新最大覆盖范围
if(i == curIndex){//当下标到达当前步所到达的下标后,开始进行移动
step ++;
//这个步数是比较宽泛的,不是实际的行走路线,只是当到达当前步所能到达的最大下标位置处,就需要开始移动了
//所以可能是跳了1个位置,也可能跳了2个位置,但是总体上是移动了一次,而记录的也就是这样的一次
curIndex = nextIndex;
//更新当前步能到达的最大的下标位置,这样在还未到达时可以判断在这个区间内所能到达的下一个最大位置下标处
if(nextIndex >= nums.size() - 1) break;
}
}
return step;
}
当然,可以对上面的代码进行一个简化,也就是循环到达nums.size()-2的地方结束循环,因为题目中说必然有解,所以当nums.size()-2是某一步的最大索引位置,那么必然还需要再走一步到达最后的位置,所以得再加1;但是如果nums.size()-2的地方不是某一步的最大索引位置,说明这一步的覆盖范围已经大于或者等于了nums.size()-1的索引位置,也就是说,步数不需要再加一了,循环也跳出了,step中记录的就是最后的结果。
int jump(vector<int>& nums) {
int curIndex = 0;//记录当前步所到达的下标位置
int nextIndex = 0;//记录下一步所到达的下标位置
int step = 0;//记录步数
for(int i = 0; i < nums.size() - 1; i ++){//注意这里是i<nums.size()-1
nextIndex = max(i + nums[i], nextIndex);//更新最大覆盖范围
if(i == curIndex){//当下标到达当前步所到达的下标后,开始进行移动
//如果说到达nums.size()-2的下标时刚好是curIndex,那么此时step会加1,
//因为题目中说一定会有解,所以不用管其他限制条件了;
//而如果说到达nums.size()-2的下标时不是curIndex,那就说明前面的一步已经包含了nums.size()-1,
//不用再进入这个条件判断了
curIndex = nextIndex;
step ++;
}
}
return step;
}
时间复杂度:O(n)
空间复杂度:O(1)
LeetCode1005.K次取反后最大化的数组和
给你一个整数数组
nums
和一个整数k
,按以下方法修改该数组:
- 选择某个下标
i
并将nums[i]
替换为-nums[i]
。重复这个过程恰好
k
次。可以多次选择同一个下标i
。以这种方式修改数组后,返回数组 可能的最大和 。
思路:这道题还是比较简单的,考虑的地方也就是当有负数的时候,将最小的负数反转;当只存在正数的时候将最小的正数进行反转,这样能够保证和能够最大。当然,反转的时候还是需要进行判断一下,比如k的数量是否比负数多,k%2等于0还是1,等等,这些情况的处理是不同的,但是都大同小异。
int largestSumAfterKNegations(vector<int>& nums, int k) {
vector<int> positive;//存放正数
vector<int> negative;//存放负数
int sum = 0;
sort(nums.begin(), nums.end());//需要排序
for(int i = 0; i < nums.size(); i ++){
sum += nums[i];
if(nums[i] >= 0) positive.push_back(nums[i]);
if(nums[i] < 0) negative.push_back(nums[i]);
}
if(negative.size() == 0){//当负数数组大小为0时,说明全为正数
if(k % 2 == 1) sum -= 2 * positive[0];//如果k值为奇数,将最小的正数反转后,更新sum
}else if(k <= negative.size()){//当负数数组大小不为0时,并且k的值小于等于负数数组大小
for(int i = 0; i < k; i ++){
sum += 2 * (0 - negative[i]);//将k范围内的每个负数元素转换为正数,并更新sum
}
}else if(k > negative.size()){//当负数数组的大小不为0,并且k的值大于负数数组的大小时
for(int i = 0; i < negative.size(); i ++){
sum += 2 * (0 - negative[i]);//将所有负数元素转换为正数,并更新sum
nums[i] = 0 - nums[i];//将nums数组中的负数元素一并更新
}
int k_left = k - negative.size();//计算剩余的k值
sort(nums.begin(), nums.end());//对nums中的元素重新按从小到大的顺序排列
if(k_left % 2 == 1) sum -= 2 * nums[0];//如果剩余的k值是奇数,那么更新sum的值
}
return sum;
}
时间复杂度:O(nlogn)
空间复杂度:O(n)
当然,还有一种思路,那就是将所有元素按照绝对值大小从大到小排列,当遇到负数并且k不为0时,将负数取反,最后判断一下k是否用完,如果用完了或者说剩下的是偶数个k,那就直接取元素求和即可;如果剩下的是奇数个k,那就说明数组里面所有的负数都转换成了正数,那我就需要取最后一个元素将其取反,因为最后一个元素就是最小的正数,然后再取各元素求总和即可得到和的最大的可能值。
static bool cmp(int a, int b){//注意这里要加上static修饰符
return abs(a) > abs(b);
}
int largestSumAfterKNegations(vector<int>& nums, int k) {
sort(nums.begin(), nums.end(), cmp);//对nums数组按照元素绝对值大小排序
for(int i = 0 ; i < nums.size(); i ++){
if(nums[i] < 0 && k > 0){
nums[i] *= -1;//将负数反转
k --;//更新k值
}
}
if(k % 2 == 1) nums[nums.size() - 1] *= -1;//当剩余的k值为奇数时,对最后一个正数,也就是最小的正数进行反转
int sum = 0;
for(int a: nums) sum += a;//统计总和
return sum;
}
时间复杂度:O(nlogn)
空间复杂度:O(1)
感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。
如果有什么问题欢迎评论区讨论!