首页 > 编程语言 >代码随想录算法训练营 day31 | 455.分发饼干 376.摆动序列 53.最大子数组和

代码随想录算法训练营 day31 | 455.分发饼干 376.摆动序列 53.最大子数组和

时间:2024-06-08 23:32:04浏览次数:11  
标签:count 前缀 nums int 随想录 455 53 result 数组

376. 摆动序列

说实话,没明白为啥算是贪心。

最开始的思路是去重,然后统计正负变化次数。

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.size()==1) return 1;
        int ans=0,last=-2,now;
        for(int i=1; i<nums.size(); i++)
        {  
            // 忽略连续的重复元素
           if(nums[i] == nums[i-1]) continue;
            // 判断当前正还是负
           now= (nums[i] - nums[i-1]) > 0 ? 1 : -1;
            // 判断方向是否发生变化,若变化,则累计变化次数(折线数)
           if(now!=last) ans++;
           // 更新上一次方向
           last=now;
        }
        //到最后的节点,算上该节点,所以++
        return ++ans;
    }
};

后面看了题解,挺多是按照动态规划解的,暂时还不会,卡哥这个题解也挺好的,这个解法不用去重,省下了一个o(n)的时间

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.size() == 1) return 1;
        if(nums.size() == 2 && nums[0] != nums[1]) return 2;

        int diff1 = nums[1] - nums[0];
        int diff2 = 0;

        int result = diff1 != 0 ? 2 : 1;

        for(int i = 2; i < nums.size(); i++){
            diff2 = nums[i] - nums[i-1];

            if((diff1 <= 0 && diff2 > 0) || (diff1 >= 0 && diff2 < 0)){
                result++;
                diff1 = diff2;
            }
        }

        return result;
    }
};

53. 最大子序和

这题暴力解法的思路,第一层 for 就是设置起始位置,第二层 for 循环遍历数组寻找最大值。

暴力是O(n^2)的,为了降低时间复杂度,回看题目,连续数组的问题,尝试滑动窗口,滑动窗口其实就是双指针的应用之一,是有效的将 n^2 降到 n 的方法。再看题目,求某个子数组的和,联想到了前缀和,然后联想到之前用前缀数组+滑动窗口的解题方法,考虑这题也这么用。但这题存在负值,无法保证单调性,用滑动窗口维护非常困难,或者说就是滑不了。所以放弃使用滑动窗口,想想怎么使用前缀和。

使用前缀和数组能快速得出 i,j 的区间和,num[ j ] - num[ i ],即可得出区间 i, j 的和。使用前缀和数组,我们就可以将寻找最大和的连续子数组的问题转换成在前缀和数组中,寻找 i, j (i < j)区间下标,使得区间的和最大,即将原来的问题转换成了,寻找前缀和数组中的最小值,以及最大值,两者的差就是区间和。

这种思路的思考过程是由于知道前缀和的技巧才能联想到,没了解过前缀和是比较难联想到的。

后续看题解学习的过程中,发现有相同思路但并不需要构造前缀和数组方法。直接在构造前缀和的过程中维护最小值和最大值即可,不需要用数组将前缀和存储起来,这样减少了空间复杂度。

class Solution {
public:
    int maxSubArray(vector<int> &nums) {
        int ans = INT_MIN;
        int min_pre_sum = 0;
        int pre_sum = 0;
        for (int x : nums) {
            pre_sum += x; // 当前的前缀和
            ans = max(ans, pre_sum - min_pre_sum); // 减去前缀和的最小值
            min_pre_sum = min(min_pre_sum, pre_sum); // 维护前缀和的最小值
        }
        return ans;
    }
};

题目有提到进阶的要求,使用分治来处理这个问题。

这里记录一下卡哥的思路,这个我确实想不到。如何用贪心来解,分析如下:


贪心贪的是哪里呢?

如果 -2 1 在一起,计算起点的时候,一定是从 1 开始计算,因为负数只会拉低总和,这就是贪心贪的地方!

局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。

全局最优:选取最大“连续和”

局部最优的情况下,并记录最大的“连续和”,可以推出全局最优

从代码角度上来讲:遍历 nums,从头开始用 count 累积,如果 count 一旦加上 nums[i]变为负数,那么就应该从 nums[i+1]开始从 0 累积 count 了,因为已经变为负数的 count,只会拖累总和。

这相当于是暴力解法中的不断调整最大子序和区间的起始位置

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int result = INT32_MIN;
        int count = 0;
        for (int i = 0; i < nums.size(); i++) {
            count += nums[i];
            if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
                result = count;
            }
            if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
        }
        return result;
    }
};

常见误区

误区一:

不少同学认为 如果输入用例都是-1,或者 都是负数,这个贪心算法跑出来的结果是 0, 这是又一次证明脑洞模拟不靠谱的经典案例,建议大家把代码运行一下试一试,就知道了,也会理解 为什么 result 要初始化为最小负数了。

误区二:

大家在使用贪心算法求解本题,经常陷入的误区,就是分不清,是遇到 负数就选择起始位置,还是连续和为负选择起始位置。

在动画演示用,大家可以发现, 4,遇到 -1 的时候,我们依然累加了,为什么呢?

因为和为 3,只要连续和还是正数就会 对后面的元素 起到增大总和的作用。 所以只要连续和为正数我们就保留。

这里也会有录友疑惑,那 4 + -1 之后 不就变小了吗? 会不会错过 4 成为最大连续和的可能性?

其实并不会,因为还有一个变量 result 一直在更新 最大的连续和,只要有更大的连续和出现,result 就更新了,那么 result 已经把 4 更新了,后面 连续和变成 3,也不会对最后结果有影响。


卡哥:“本题的贪心思路其实并不好想,这也进一步验证了,别看贪心理论很直白,有时候看似是常识,但贪心的题目一点都不简单!”

这下放心了,确实不好想,不是我笨,哈哈哈

标签:count,前缀,nums,int,随想录,455,53,result,数组
From: https://blog.csdn.net/xl1052524603/article/details/139548015

相关文章

  • 代码随想录第4天 | 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 0
    题目:24.两两交换链表中的节点思路:设置虚拟头结点,双指针+临时指针,(感觉也能递归,未尝试)时间复杂度:O(n)空间复杂度:O(1)坑:1.又忘了else{}和return2.试图访问空指针,多个条件的顺序问题及"&&""||"问题,cur->next要写在cur->next->next前面/***Definitionforsingly-linked......
  • 代码随想录算法训练营第三十二天 | 122.买卖股票的最佳时机 55.跳跃游戏 45.跳跃游戏I
    122.买卖股票的最佳时机II题目链接文章讲解视频讲解思路:每次记录当天的股票价格,如果下一天比今天的价钱高那么今天就买,这样保证每一次买股票都是赚的否则记录下一天的股票,因为下一天的股票比今天的便宜,下一天买比今天买划算classSolution{public:intmaxProfit(v......
  • 代码随想录算法训练营第四天 |
    24.两两交换链表中的节点题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。解题:关键:cur的位置在要交换的两个节点的前面具体如何交换的操作!!while种植条件:cur的下一个和下下个都不为空,不......
  • 代码随想录算法训练营第四天 Leetcode 24 两两交换链表节点 Leetcode19 删除链表倒数
    链表问题首先要记住设置虚拟头节点Leetcode24两两交换链表节点题目链接思路:就是简单模拟两两交换 要注意链表节点的处理一定要获取到合适的位置比如:这一题中两个交换节点的前一个节点注意链表保存临时节点/***Definitionforsingly-linkedlist.*publicclas......
  • 代码随想录算法训练营第五十一天| 121. 买卖股票的最佳时机、122.买卖股票的最佳时机I
    121.买卖股票的最佳时机一只股票只能买卖一次代码随想录.-力扣(LeetCode)输入:[7,1,5,3,6,4]输出:5解释:在第2天(股票价格=1)的时候买入,在第5天(股票价格=6)的时候卖出,最大利润=6-1=5。注意利润不能是7-1=6,因为卖出价格需要大于买入价格;同时,你不能在买入......
  • 代码随想录算法训练营第五十天| 198.打家劫舍、213.打家劫舍II、337.打家劫舍 III
    198.打家劫舍文档讲解:代码随想录题目链接:.-力扣(LeetCode) 问题:计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。也就是说相邻的房间不能偷当前房屋偷与不偷取决于前一个房屋和前两个房屋是否被偷了。所以这里就更感觉到,当前状态和前面状态会有一种依赖......
  • P10553 [ICPC2024 Xi'an I] Guess The Tree 题解
    挺有意思的题。思路考虑一个比较自然的做法。我们每次对于一棵树,我们将它的某一条链抽出来。这样,我们只需要知道这颗树的所有节点与链底的\(\text{lca}\),就可以知道它是属于这条链上哪一个节点的下面。然后就可以递归处理。由于交互库不是自适应的。我们可能可以想到随机......
  • atcoder ABC 353-A题详解
    atcoderABC353-A题详解ProblemStatementThereareNbuildingsalignedinarow.Thei-thbuildingfromthelefthasaheightofHi.Determineifthereisabuildingtallerthanthefirstonefromtheleft.Ifsuchabuildingexists,findtheposition......
  • 代码随想录算法训练营第三十一天 | 455.分发饼干 376.摆动序列 53.最大子数组和
    455.分发饼干题目链接文章讲解视频讲解classSolution{public:intfindContentChildren(vector<int>&g,vector<int>&s){sort(g.begin(),g.end());sort(s.begin(),s.end());intindex=0;//从最小的饼干开始遍历f......
  • 代码随想录第3天 | 链表 203.移除链表元素,707.设计链表,206.反转链表
    题目:203.移除链表元素思路:主要是头节点的删除问题,一种是直接在原链表上后移头节点设置虚拟头结点,指向原链表的头结点,在设置一个cur指针指向当前节点,虚拟头节点初始化后就不移动了,使用cur进行移动不要忘记释放删除节点的内存,自行设置的虚拟头节点也要释放时间复杂度:O(n)空......