45 跳跃游戏2(贪心算法)
1. 跳跃游戏 2(贪心算法)
给定一个长度为 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;
}
};
1.解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内⼀定是可以跳到的,以最⼩的步数增加覆盖范围,覆盖范围⼀旦覆盖了终点,得到的就是最⼩步数! 这⾥需要统计两个覆盖范围,当前这⼀步的最⼤覆盖和下⼀步最⼤覆盖
2.时间复杂度: O(n) 空间复杂度: O(1)
2.255 跳跃游戏(贪心算法)
2.跳跃游戏(贪心算法)
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int temp = 0;
for (int i = 0; i < n; i++) {
if (i <= temp) {//如果当前位置小于所能移动的最大值
temp = max(nums[i] + i, temp);
if ((temp) >= n - 1) {
return true;
}
}
}
return false;
}
};
1.条件 i <= temp
的含义是:
- 如果当前索引
i
小于或等于temp
,这意味着我们目前能够到达索引i
。 - 在这种情况下,我们可以尝试从索引
i
跳到更远的位置,即nums[i] + i
,这是从索引i
开始,能够跳到的最远位置。 - 如果我们能够跳到更远的位置,我们就更新
temp
为max(nums[i] + i, temp)
,这样temp
就始终存储了从起点开始,能够跳到的最远位置。
如果当前索引 i
大于 temp
,这意味着我们无法到达索引 i
,因此我们不需要更新 temp
,并且可以直接跳出循环,因为数组中存在一个无法到达的“缺口”,我们无法跳过它到达数组的末尾。
总结一下,if (i <= temp)
这个条件用于决定是否应该更新 temp
,即是否应该考虑从当前位置 i
跳到更远的位置。如果当前索引 i
小于或等于 temp
,我们就更新 temp
;否则,我们继续遍历数组。
3.LCR 104 组合总数IV(动态规划)
LCR 104 组合总数IV //排列数
给定一个由 不同 正整数组成的数组 nums
,和一个目标整数 target
。请从 nums
中找出并返回总和为 target
的元素组合的个数。数组中的数字可以在一次排列中出现任意次,但是顺序不同的序列被视作不同的组合。
题目数据保证答案符合 32 位整数范围。
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target + 1, 0);
dp[0] = 1;
for (int j = 0; j <= target; j++) { // 遍历背包
for (int i = 0; i < nums.size(); i++) { // 遍历物品
if (j - nums[i] >= 0 && dp[j] < INT_MAX - dp[j - nums[i]]) {
dp[j] += dp[j - nums[i]];
}
}
}
return dp[target];
}
};
1.确定遍历顺序 个数可以不限使⽤,说明这是⼀个完全背包。
得到的集合是排列,说明需要考虑元素之间的顺序。
本题要求的是排列,那么这个for循环嵌套的顺序可以有说法了。
*如果求组合数就是外层for循环遍历物品,内层for遍历背包。
*如果求排列数就是外层for遍历背包,内层for循环遍历物品。
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,
举⼀个例⼦:计算dp[4]的时候,结果集只 有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后⾯! 所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
2.for (int j = 0; j <= target; j++) int j = 0的意义
for (int j = 0; j <= target; j++)
这行代码是一个循环,它用于遍历背包的容量。这里的背包是指一个容量从 0 到 target
的虚拟背包,我们想要找到和为 target
的不同组合的数量,其中可以使用 nums
中的数字无数次。
这个循环的目的是为了计算和为每个可能的值 j
的不同组合的数量。循环变量 j
从 0 开始,一直到 target
,因为我们需要计算从 0 到 target
每个和值的组合数量。
在每次循环中,我们使用内层循环遍历 nums
数组,来考虑每个可能的数字。如果当前数字 nums[i]
小于等于 j
(即背包的当前容量),那么我们可以将这个数字加入到组合中。此时,和为 j
的组合数量就会增加和为 j - nums[i]
的组合数量,因为我们可以将和为 j - nums[i]
的任何组合加上一个 nums[i]
来得到和为 j
的新组合。
因此,dp[j]
的值是在每次迭代中更新的,以包含所有可能的组合方式,使得和为 j
。
这个循环是动态规划解决方案的一部分,它利用了子问题的解来构建更大问题的解。dp
数组中的每个元素 dp[j]
存储了和为 j
的不同组合的数量,而这个数量是通过考虑所有小于 j
的和值并将它们与 nums
中的数字组合起来得到的。
最终,当循环完成时,dp[target]
就会包含和为 target
的不同组合的数量,
3.时间复杂度: O(target * n),其中 n 为 nums 的⻓度
空间复杂度: O(target)
4.C++测试⽤例有两个数相加超过int的数据,所以需要在if⾥加上dp[i] < INT_MAX - dp[i - num]
if (j - nums[i] >= 0 && dp[j] < INT_MAX - dp[j - nums[i]])
是一个条件判断语句,用于防止整数溢出并确保数组索引的有效性。
让我们分解这个条件:
-
j - nums[i] >= 0
: 这个条件检查当前背包的容量j
减去当前考虑的数字nums[i]
是否大于或等于 0。如果是,这意味着我们可以将数字nums[i]
放入背包中,因为它的价值不会超过背包的当前容量。 -
dp[j] < INT_MAX - dp[j - nums[i]]
: 这个条件是为了防止整数溢出。在计算dp[j]
时,我们需要将dp[j - nums[i]]
加到dp[j]
上。如果dp[j - nums[i]]
很大,那么dp[j] + dp[j - nums[i]]
可能会超过INT_MAX
,这是int
类型能表示的最大值。这个条件确保了在加法操作之前,dp[j]
加上dp[j - nums[i]]
不会超过INT_MAX
。
4.买卖股票的最佳时机2(贪心算法)
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
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;
}
};
我们需要收集每天的正利润就可以,收集正利润的区间,result+=max(prices[i]-prices[i-1],0);就是股票买卖的区间,⽽我们只需 要关注最终利润,不需要记录区间。 那么只收集正利润就是贪⼼所贪的地⽅!
局部最优:收集每天的正利润, 全局最优:求得最⼤利润。
5.最大子数组和(贪心算法)
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组
是数组中的一个连续部分。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int count=0;//第i个过程中的最大和
int result=INT_MIN;//最终和
for(int i=0;i<nums.size();i++){
count+=nums[i];
if(count>result){
result=count;
}
if(count<=0){
count=0;
}
}
return result;
}
};
1.局部最优:当前“连续和”为负数的时候⽴刻放弃,从下⼀个元素重新计算“连续和”,因为负数加上下⼀个元素 “连续 和”只会越来越⼩。
全局最优:选取最⼤“连续和” 局部最优的情况下,并记录最⼤的“连续和”,可以推出全局最优。
从代码⻆度上来讲:遍历 nums,从头开始⽤ count 累积,如果 count ⼀旦加上 nums[i]变为负数,那么就应该从 nums[i+1]开始从 0 累积 count 了,因为已经变为负数的 count,只会拖累总和
2.if(count<=0){
count=0;这句话的意思是 当当前的子序列和小于0时 直接抛弃不使用 因为如果 count ⼀旦加上 nums[i]变为负数,只会拖累总和
6.摆动序列(贪心算法)
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
-
例如,
[1, 7, 4, 9, 2, 5]
是一个 摆动序列 ,因为差值(6, -3, 5, -7, 3)
是正负交替出现的。 - 相反,
[1, 4, 7, 2, 5]
和[1, 7, 4, 5, 5]
不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums
,返回 nums
中作为 摆动序列 的 最长子序列的长度 。
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size()==1)return 1;
int result=1;
int pre=0,cur=0;
for(int i=0;i<nums.size()-1;i++){
cur=nums[i+1]-nums[i];
if((pre<=0&&cur>0)||(pre>=0&&cur<0)){
result++;
pre=cur;
}
}
return result;
}
};
1.代码分析
给你一个整数数组 nums
,返回 nums
中作为 摆动序列 的 最长子序列的长度 。
在这段代码中,wiggleMaxLength
函数的目的是计算一个整数数组 nums
的“摆动序列”的长度。摆动序列是指相邻元素之间差值正负交替的序列,例如,[1, 7, 4, 9, 2, 5]
是一个摆动序列,因为相邻元素的差值交替为正和负。
让我们逐行解释代码:
-
if(nums.size()==1)return 1;
:如果数组nums
只有一个元素,那么它本身就是摆动序列,长度为 1。 -
int result=1;
:初始化摆动序列的长度为 1,因为无论数组如何,至少有一个元素是摆动序列的一部分。 -
int pre=0,cur=0;
:初始化两个变量pre
和cur
,用于存储相邻元素之间的差值。pre
存储前一对相邻元素的差值,cur
存储当前对相邻元素的差值。 -
for(int i=0;i<nums.size()-1;i++)
:遍历数组nums
,但是只遍历到nums.size()-2
的位置,因为我们需要比较nums[i]
和nums[i+1]
。 -
cur=nums[i+1]-nums[i];
:计算当前对相邻元素的差值。 -
if((pre<=0&&cur>0)||(pre>=0&&cur<0))
:这个条件用于检查当前差值cur
是否与前一个差值pre
正负不同。如果pre
是非正数(包括 0 和负数)且cur
是正数,或者pre
是非负数且cur
是负数,那么这意味着我们找到了一个新的摆动。 -
result++;
:如果找到了一个新的摆动,增加摆动序列的长度。 -
pre=cur;
:更新pre
为当前的差值cur
,为下一次比较做准备。
最后,函数返回 result
,即摆动序列的长度。
这个算法的时间复杂度是 O(n),其中 n 是数组 nums
的长度,因为我们只需要遍历数组一次。空间复杂度是 O(1),因为我们只使用了几个额外的变量。