代码随想录算法训练营Day27 | 56.合并区间、738.单调递增的数字、968.监控二叉树

LeetCode56.合并区间

56. 合并区间 - 力扣(LeetCode)

1.思路

思路和 用最少数量的弓箭引爆气球 一样,按照左边界从小到大排序后,for 从 1 开始遍历,先记录上一个区间的左边界 l 和右边界 r ,然后如果下一个的左边界小于等于上一个的右边界(即 intervals[i][0] <= intervals[i - 1][1]),说明这两个区间是重合的,然后使用 while 循环判断后面是否还有区间和当前区间重合,更新右边界,最后存入答案集(ans.push_back({l,r}))。

cpp 复制代码
class Solution {
public:
    static bool cmp(vector<int>& a,vector<int>& b){
        return a[0]<b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end(),cmp);
        vector<vector<int>>ans;
        for(int i=1;i<=intervals.size();i++){
            int l=intervals[i-1][0];
            int r=intervals[i-1][1];
            while(i<intervals.size() && intervals[i][0]<=intervals[i-1][1]){
                intervals[i][1]=max(intervals[i][1],intervals[i-1][1]);
                r=intervals[i][1];
                i++;
            }
            ans.push_back({l,r});
        }
        return ans;
    }
};

注:

  1. 这里 for 循环要遍历到 intervals.size() 停止,因为是从 1 开始遍历的,每次存的是前一个合并的区间,要想把最后一个区间也存入数组,必须遍历到原数组末尾的下一位;

  2. 因为要遍历到 intervals.size() ,所以第一个前提就是 i<intervals.size() ,然后才能接着判断是否存在连续的重叠区间;

  3. while 里最后的 i++ :因为实际处理的是 i 前面的重叠区间,所以找到右边界后需要 i++ ,这样才不会重复放入 i 处的区间。

优化

为了避免不断寻找右边界,可以直接对 ans 的末尾修改。只需用 ans.back()[1] 来和 intervals[i][0] 来做比较,判断是否有重叠区间,有的话就修改 ans.back()[1] 为最大的右边界;没有重叠区间就直接放进 ans 里就行。

cpp 复制代码
class Solution {
public:
    static bool cmp(vector<int>& a,vector<int>& b){
        return a[0]<b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end(),cmp);
        vector<vector<int>>ans;
        if(intervals.size()==0) return ans;
        ans.push_back(intervals[0]);
        for(int i=1;i<intervals.size();i++){
            if(intervals[i][0] <= ans.back()[1]){
                ans.back()[1]=max(ans.back()[1],intervals[i][1]);
            }
            else{
                ans.push_back(intervals[i]);
            }
        }
        return ans;
    }
};

2.复杂度分析

时间复杂度:O(nlog n)

空间复杂度:O(logn)

3.思考

按照 用最少数量的弓箭引爆气球 的思路,这道题还是很好想的,固定左边界,寻找不重叠的最大右边界;第二种方法属实震惊到我了,我只想到了更新右边界,没想到可以先把区间放入数组种,如果有重叠区间,再更新数组末位的值就行了。对容器的使用更深一步理解了。

4.Reference:56. 合并区间 | 代码随想录


LeetCode738.单调递增的数字

738. 单调递增的数字 - 力扣(LeetCode)

1.思路

这道题需要想清楚个例,例如 98 ,一旦出现 a[i-1] > a[i] 的情况,首先让 a[i-1]--,a[i]='9',这样下来最接近 98 的单调递增数字就是 89 。

那么是怎样遍历呢?这里必须为从后往前遍历,举个例子 323,从前往后遍历得到的是329,从后往前则是299 (332->329->299) ;

在遍历遇到 a[i-1] > a[i] 时,不能直接就给 a[i] 赋值为 '9',举个例子 100,如果直接赋值的话,最终结果就是 90 ,这与正确答案 99 不同,所以不能在遍历时直接修改,而是需要用 index 存下标,用来标记从哪个位置开始赋值 '9',最后通过 for 循环来赋值。

cpp 复制代码
class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        string a = to_string(n);
        int index=a.size();
        for(int i=a.size()-1;i>0;i--){
            if(a[i-1]>a[i]){
                a[i-1]--;
                index=i;
            }
        }
        for(int i=index;i<a.size();i++){
            a[i]='9';
        }
        return stoi(a);
    }
};

2.复杂度分析

时间复杂度:O(n)

空间复杂度:O(n)

3.思考

本题难点在于要想清楚个例,一旦出现 a[i-1] > a[i] 的情况,首先让 a[i-1]--,a[i]='9' ;

还要想好遍历的顺序,从前往后和从后往前遍历是截然不同的;

最后就是不能直接在遍历时给 a[i] 赋值'9',而是先通过 index 存下标,最后 for 循环来赋值。

4.Reference:738.单调递增的数字 | 代码随想录


LeetCode968.监控二叉树

968. 监控二叉树 - 力扣(LeetCode)

1.思路

难难难!

这里需要用到递归,先考虑每个节点的状态:

0:该节点无覆盖

1:本节点有摄像头

2:本节点有覆盖

依旧递归三部曲:

  1. 返回 int(代表该节点的状态),传入节点,定义一个全局变量 res 存答案;

  2. 遇到空节点返回 2(空节点的状态只能是有覆盖,这样就可以在叶子节点的父节点放摄像头了);

  3. 后序遍历,先收集左右孩子的状态,再分情况讨论:

情况1:左右节点都有覆盖

return 0 ,此时中间节点就是无覆盖的状态了。

情况2:左右节点至少有一个无覆盖的情况

有一个孩子没有覆盖,父节点就应该放摄像头。此时摄像头的数量要加一,并且return 1,代 表中间节点放摄像头。

情况3:左右节点至少有一个有摄像头

return 2 ,代表父节点是覆盖的状态。

最后判断根节点是否是情况1,如果是的话,就单独让 res++;

cpp 复制代码
class Solution {
public:
    int res=0;
    int traversal(TreeNode* node){
        if(node==NULL) return 2;
        int left = traversal(node->left);
        int right = traversal(node->right);
        // 情况1
        // 左右节点都有覆盖
        if(left==2 && right==2){
            return 0;
        }
        // 情况2
        // left == 0 && right == 0 左右节点无覆盖
        // left == 1 && right == 0 左节点有摄像头,右节点无覆盖
        // left == 0 && right == 1 左节点有无覆盖,右节点摄像头
        // left == 0 && right == 2 左节点无覆盖,右节点覆盖
        // left == 2 && right == 0 左节点覆盖,右节点无覆盖
        if(left==0 || right==0){
            res++;
            return 1;
        }
        // 情况3
        // left == 1 && right == 2 左节点有摄像头,右节点有覆盖
        // left == 2 && right == 1 左节点有覆盖,右节点有摄像头
        // left == 1 && right == 1 左右节点都有摄像头
        if(left==1 || right==1){
            return 2;
        }
        return -1;
    }
    int minCameraCover(TreeNode* root) {
        // 情况4 root 无覆盖
        if(traversal(root)==0) res++;
        return res;
    }
};

2.复杂度分析

时间复杂度:O(n)

空间复杂度:O(n)

3.思考

本题的难点首先是要想到贪心的思路,然后就是遍历和状态推导。

在二叉树上进行状态推导,难度上了一个台阶了。这道题目是名副其实的hard,难难难!

4.Reference:968.监控二叉树 | 代码随想录


贪心算法总结

一、理论

  1. 核心逻辑:无需严格数学证明,手动模拟无反例、能自圆其说,即可尝试贪心思路。
  2. 判定标准:若能找到明确的 "局部最优选择",且该选择可累积成 "全局最优结果",则适合用贪心。
  3. 学习关键:不纠结 "是否为纯贪心题",重点掌握解题思路,结合题目特点灵活应用。

二、刷题

  1. 刷题顺序:不严格按 "简单到困难",而是简单与困难交错,整体呈阶梯式上升。
  2. 与回溯法差异:回溯需严格按框架顺序刷题(前后题目有因果关系),贪心无此限制。
  3. 额外要求:需理解所用编程语言的内部机制(如 C++ 中 list 与 vector 的效率差异),才能写出高效代码。

三、题型分类与要点

1. 简单题(常识类)

  • 特点:思路贴近常识,重点是明确 "局部最优" 与 "全局最优" 的对应关系。
  • 代表题目:分发饼干、K 次取反后最大化的数组和、柠檬水找零。

2. 中等题(巧思类)

  • 特点:仅靠常识难以突破,需挖掘题目隐藏的 "最优逻辑"。
  • 代表题目:摆动序列、单调递增的数字。

3. 股票问题(贪心应用类)

  • 特点:动态规划是专长,但贪心可实现更简洁的解法。
  • 代表题目:买卖股票的最佳时机 II、买卖股票的最佳时机含手续费(后者贪心较绕,可结合动规理解)。

4. 双维度权衡问题(优先级类)

  • 特点:两个维度相互影响,需先固定一个维度排序,再处理另一个维度,避免顾此失彼。
  • 代表题目:分发糖果、根据身高重建队列。

5. 区间问题(高频重点类)

  • 特点:围绕区间的覆盖、去重、合并等操作,贪心思路集中且典型。
  • 代表题目:跳跃游戏、跳跃游戏 II、用最少数量的箭引爆气球、无重叠区间、划分字母区间、合并区间。

6. 其他难题(综合类)

  • 特点:需结合其他知识点(如二叉树、数组操作),贪心思路隐蔽,需反复练习。
  • 代表题目:最大子序和(贪心比动规更优)、加油站、监控二叉树。

四、学习建议

  1. 多刷重复题:难题(如区间问题、监控二叉树)需反复练习,强化贪心 "感觉"。
  2. 聚焦局部最优:解题时先明确 "当前步的最优选择是什么",再验证是否能推导全局最优。
  3. 结合其他算法:部分题目(如股票、最大子序和)可对比贪心与动规的解法,加深理解。
相关推荐
信奥卷王1 小时前
2025年9月GESPC++三级真题解析(含视频)
开发语言·c++·算法
努力学习的小廉1 小时前
我爱学算法之—— BFS之FLoodFill算法
算法·宽度优先
天选之女wow2 小时前
【Hard——Day8】65.有效数字、68.文本左右对齐、76.最小覆盖子串
linux·运维·redis·算法·leetcode
AI大模型学徒2 小时前
NLP基础(八)_马尔可夫模型
算法·机器学习·自然语言处理·nlp·概率论·马尔可夫模型
前端小L2 小时前
图论专题(十八):“逆向”拓扑排序——寻找图中的「最终安全状态」
数据结构·算法·安全·深度优先·图论·宽度优先
前端小L2 小时前
图论专题(十七):从“判定”到“构造”——生成一份完美的「课程表 II」
算法·矩阵·深度优先·图论·宽度优先
qq_433554543 小时前
C++ 稀疏表
开发语言·c++·算法
小白程序员成长日记3 小时前
2025.11.21 力扣每日一题
算法·leetcode·职场和发展
小年糕是糕手4 小时前
【C++】C++入门 -- inline、nullptr
linux·开发语言·jvm·数据结构·c++·算法·排序算法