贪心算法 2024.5.16 5题

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]]) 是一个条件判断语句,用于防止整数溢出并确保数组索引的有效性。

让我们分解这个条件:

  1. j - nums[i] >= 0: 这个条件检查当前背包的容量 j 减去当前考虑的数字 nums[i] 是否大于或等于 0。如果是,这意味着我们可以将数字 nums[i] 放入背包中,因为它的价值不会超过背包的当前容量。

  2. 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(贪心算法)

买卖股票的最佳时机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.最大子数组和(贪心算法)

53.最大子数组和

给你一个整数数组 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.摆动序列(贪心算法)

376.摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如, [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] 是一个摆动序列,因为相邻元素的差值交替为正和负。

让我们逐行解释代码:

  1. if(nums.size()==1)return 1;:如果数组 nums 只有一个元素,那么它本身就是摆动序列,长度为 1。

  2. int result=1;:初始化摆动序列的长度为 1,因为无论数组如何,至少有一个元素是摆动序列的一部分。

  3. int pre=0,cur=0;:初始化两个变量 pre 和 cur,用于存储相邻元素之间的差值。pre 存储前一对相邻元素的差值,cur 存储当前对相邻元素的差值。

  4. for(int i=0;i<nums.size()-1;i++):遍历数组 nums,但是只遍历到 nums.size()-2 的位置,因为我们需要比较 nums[i] 和 nums[i+1]

  5. cur=nums[i+1]-nums[i];:计算当前对相邻元素的差值。

  6. if((pre<=0&&cur>0)||(pre>=0&&cur<0)):这个条件用于检查当前差值 cur 是否与前一个差值 pre 正负不同。如果 pre 是非正数(包括 0 和负数)且 cur 是正数,或者 pre 是非负数且 cur 是负数,那么这意味着我们找到了一个新的摆动。

  7. result++;:如果找到了一个新的摆动,增加摆动序列的长度。

  8. pre=cur;:更新 pre 为当前的差值 cur,为下一次比较做准备。

最后,函数返回 result,即摆动序列的长度。

这个算法的时间复杂度是 O(n),其中 n 是数组 nums 的长度,因为我们只需要遍历数组一次。空间复杂度是 O(1),因为我们只使用了几个额外的变量。

 

  • 24
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值