首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >CCPC贪心专练(代码随想录篇)

CCPC贪心专练(代码随想录篇)

作者头像
十二.
发布2025-10-22 15:28:34
发布2025-10-22 15:28:34
1790
举报

《算法设计与分析》在讲解贪心基础的时候,将贪心的特性归纳为两点最优子结构贪心选择性质

  • 最优子结构:问题的最优解,可以分解为子问题的最优解
  • 贪心选择性质:从局部最优解,推至全局最优解

最优子结构:从(1~8中,取最小值)

贪心选择性质:(状态3是在状态2推导出来的,与状态1无关。这也是贪心与动归的核心区别)

大概明白概念就行,更多的,还是在题目中锻炼出来的。


一、分发饼干 -- 尽量满足大胃口

二、摆动序列 -- 状态跟踪-很帅😉

三、最大子数组和 -- 巧妙避开上一个状态的影响

四、跳跃游戏 -- 及时止损,增大收益

五、跳跃游戏II -- 尽量增加覆盖范围

六、K次取反后最大化的数组和 -- 最大化收益,最小化损失

七、加油站 -- 通过排除失败情况,快速排除不可能的起点。

八、分发糖果 -- 两次单向遍历,处理了每个节点与[左][右]两邻居的约束关系😇

九、根据身高重建队列 -- 同样是两段策略

十、用最少数量的箭引爆气球 -- 区间问题

十一、无重叠区间 -- 区间问题

十二、划分字母区间 -- 记录最后出现的值

十三、单调递增的数字 -- 从右往左,局部调整,之后数字置位9

十四、监控二叉树 -- 递推&贪心

根据多次重刷经验(3、5、7、8、9、11、12、13、14,这几题,是略有挑战的题😉)


一、分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 示例 1:

  • 输入: g = [1,2,3], s = [1,1]
  • 输出: 1 解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1,2,3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以你应该输出 1。

示例 2:

  • 输入: g = [1,2], s = [1,2,3]
  • 输出: 2
  • 解释:你有两个孩子和三块小饼干,2 个孩子的胃口值分别是 1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2.

提示:

  • 1 <= g.length <= 3 * 10^4
  • 0 <= s.length <= 3 * 10^4
  • 1 <= g[i], s[j] <= 2^31 - 1
代码语言:javascript
复制
class Solution {
// 核心思想是用尽可能大的饼干满足尽可能大的胃口,从而最大化能满足的孩子数量。
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int cnt = 0;
        int l =g.size()-1, r = s.size()-1;
        while(l>=0&&r>=0){
            if(g[l]<=s[r]){
                r--,l--;
                cnt++;
            }else{
                l--;
            }
        }
        return cnt;
    }
};
二、摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。 例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。 给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。 示例 1:

  • 输入: [1,7,4,9,2,5]
  • 输出: 6
  • 解释: 整个序列均为摆动序列。

示例 2:

  • 输入: [1,17,5,10,13,15,10,5,16,8]
  • 输出: 7
  • 解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。

示例 3:

  • 输入: [1,2,3,4,5,6,7,8,9]
  • 输出: 2

// 没有见过这么帅的贪心策略,“状态跟踪” // 记录前一个差值的方向(上升 / 下降),并动态更新当前最长长度

代码语言:javascript
复制
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
    int up = 1, down = 1; // 分别记录以当前元素结尾的上升/下降摆动序列长度
    for (int i = 1; i < nums.size(); ++i) {
        int diff = nums[i] - nums[i-1];
        if (diff > 0) up = down + 1;    // 当前是上升,只能由前一个下降的序列延长
        else if (diff < 0) down = up + 1; // 当前是下降,只能由前一个上升的序列延长
        // 差值为0时,up和down保持不变(不影响结果)
    }
    return max(up, down);
    }
};
三、最大子数组和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 示例:

  • 输入: [-2,1,-3,4,-1,2,1,-5,4]
  • 输出: 6
  • 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6
代码语言:javascript
复制
class Solution {
    // 设计还需巧妙,巧妙的避开,上一个状态对自身的影响
public:
    int maxSubArray(vector<int>& nums) {
        int sum = nums[0];
        int maxN = nums[0];
        for(int i=1; i<nums.size(); ++i){
            sum = sum<0?0:sum; // 必须及时更新这个状态
            sum+=nums[i];
            maxN = max(maxN,sum);
        }
        return maxN;
    }
};
四、跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个位置。 示例 1:

  • 输入: [2,3,1,1,4]
  • 输出: true
  • 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

示例 2:

  • 输入: [3,2,1,0,4]
  • 输出: false
  • 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

// 贪心策略:及时止损,只保留对后续有增益的子数组前缀

代码语言:javascript
复制
class Solution {
public:
    bool canJump(vector<int>& nums) {
        // 有一种,情况,经常会被忽略掉,就是最后一位是0的话,依旧会被跳过。
        int maxFlag = nums[0];
        for(int i=0; i<nums.size()-1; ++i){
            if(maxFlag==0&&maxFlag==nums[i]) return false;
            maxFlag = maxFlag<nums[i]?nums[i]:maxFlag;
            maxFlag--;
        }
        return true;
    }
};
五、跳跃游戏II

给定一个非负整数数组,你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 示例:

  • 输入: [2,3,1,1,4]
  • 输出: 2
  • 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

说明: 假设你总是可以到达数组的最后一个位置。

// 贪心策略:每次跳跃时,选择覆盖范围最大的位置,从而减少后续需要的跳跃次数

代码语言:javascript
复制
class Solution {
    // 总是对这种特殊位置,判断不清晰,本题就是错在了这种特殊位置上
public:
    int jump(vector<int>& nums) {
        if(nums.size()<=1) return 0;
        // 不断更新,能够抵达的最远距离
        int cur_n = nums[0],max_n=nums[0];
        int cnt = 0; // 次数
        for(int i=0; i<nums.size()-1; ++i){ // 来,让我爽一把
            max_n = max(max_n,i+nums[i]); 
            if(cur_n==i){ // 到了不得不蹦的位置
                cnt++;
                cur_n=max_n;
            }
        }
        return cnt+1;
    }
};
六、K次取反后最大化的数组和

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。) 以这种方式修改数组后,返回数组可能的最大和。 示例 1:

  • 输入:A = [4,2,3], K = 1
  • 输出:5
  • 解释:选择索引 (1) ,然后 A 变为 [4,-2,3]。

示例 2:

  • 输入:A = [3,-1,0,2], K = 3
  • 输出:6
  • 解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]。

示例 3:

  • 输入:A = [2,-3,-1,5,-4], K = 2
  • 输出:13
  • 解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1,5,4]。

提示:

  • 1 <= A.length <= 10000
  • 1 <= K <= 10000
  • -100 <= A[i] <= 100

// 贪心策略:优先对绝对值大的负数取反(最大化增益),若所有负数处理完仍有剩余操作次数,则对绝对值最小的数(无论正负)进行奇数次取反(最小化损失)。 最大化收益,最小化损失

代码语言:javascript
复制
class Solution {
    // 这道题目的贪心,好帅啊,可以重复练习几次,可以记录下来的呢。
    static bool cmp(int a,int b){
        return abs(a)<abs(b);
    }
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end(), cmp);
        int sum = 0;
        for(int i=nums.size()-1; i>=0; --i){
            if(nums[i]<0&&k!=0){
                k--;
                sum-=nums[i];
            }else sum+=nums[i];
        }
        if(k>0&&k%2!=0){ // k还剩余
            sum-=2*abs(nums[0]); 
        }
        return sum;
    }
};
七、加油站

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。 说明:

  • 如果题目有解,该答案即为唯一答案。
  • 输入数组均为非空数组,且长度相同。
  • 输入数组中的元素均为非负数。

示例 1: 输入:

  • gas = [1,2,3,4,5]
  • cost = [3,4,5,1,2]

输出: 3 解释:

  • 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
  • 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
  • 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
  • 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
  • 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
  • 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
  • 因此,3 可为起始索引。

示例 2: 输入:

  • gas = [2,3,4]
  • cost = [3,4,3]
  • 输出: -1
  • 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油。开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油。开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油。你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。因此,无论怎样,你都不可能绕环路行驶一周。

// 贪心策略:通过排除失败情况,快速排除不可能的起点。

代码语言:javascript
复制
class Solution {
    // 通过排除局部的失败情况,快速排除不可能的起点。
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        vector<int> diff(gas.size());
        int sum = 0;
        for(int i=0; i<gas.size(); ++i){
            diff[i] = gas[i]-cost[i];
            sum += diff[i];
        }
        if(sum<0) return -1;
        int cnt = 0;
        int cur = 0;
        for(int i=0; i<diff.size(); ++i){
            if(cur+diff[i]<0){
                cnt = i+1;
                cur = 0;
            }else cur += diff[i];
        }
        return cnt;
    }
};
八、分发糖果

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。 你需要按照以下要求,帮助老师给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢? 示例 1:

  • 输入: [1,0,2]
  • 输出: 5
  • 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。

示例 2:

  • 输入: [1,2,2]
  • 输出: 4
  • 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。

// 贪心策略:通过,两次单向遍历,处理了每个节点与[左][右]两邻居的约束关系,从而求出局部最优解,进而得出全局最优

代码语言:javascript
复制
class Solution {
public:
    int candy(vector<int>& ratings) {
        int len = ratings.size();
        vector<int> randy(len,0);
        // 从左向右 来一次
        randy[0]=1;
        for(int i=1; i<len; ++i){
            if(ratings[i]>ratings[i-1]) randy[i]=randy[i-1]+1;
            else randy[i]=1;
        }
        // 从右向左 来一次 被用来修补修补
        for(int i=len-2; i>=0; --i){
            if(ratings[i]>ratings[i+1]&&randy[i]<=randy[i+1]) randy[i] = randy[i+1]+1;
        }
        int sum = 0;
        for(int i : randy) sum+=i; 
        return sum;
    }
};
九、根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。 请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。 示例 1:

  • 输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
  • 输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
  • 解释:
    • 编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
    • 编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
    • 编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
    • 编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
    • 编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
    • 编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
    • 因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

  • 输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
  • 输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

提示:

  • 1 <= people.length <= 2000
  • 0 <= hi <= 10^6
  • 0 <= ki < people.length

题目数据确保队列可以被重建

// 贪心策略:两端策略,首先看左端,然后看右端。每次只是单 一 一 方面。 多方面一起,只会让人迷糊。

代码语言:javascript
复制
class Solution {
    // 我觉得,这是一道非常酷的题,非常有意思的呢
    // 非常有记录的必要
    static bool cmp(vector<int> a1, vector<int> a2){
        if(a1[0]==a2[0]) return a1[1]<a2[1]; // 从小到大排序
        return a1[0]>a2[0]; // 从大到小排序
    }
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        vector<vector<int>> res;
        
        for(int i=0; i<people.size(); ++i){
            if(people[i][1]==0) res.insert(res.begin()+people[i][1],people[i]);
            else res.insert(res.begin()+people[i][1],people[i]);
        }

        return res;
    }
};
十、用最少数量的箭引爆气球

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。 一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。 给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。 示例 1:

  • 输入:points = [[10,16],[2,8],[1,6],[7,12]]
  • 输出:2
  • 解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球

示例 2:

  • 输入:points = [[1,2],[3,4],[5,6],[7,8]]
  • 输出:4

示例 3:

  • 输入:points = [[1,2],[2,3],[3,4],[4,5]]
  • 输出:2

示例 4:

  • 输入:points = [[1,2]]
  • 输出:1

示例 5:

  • 输入:points = [[2,3],[2,3]]
  • 输出:1

提示:

  • 0 <= points.length <= 10^4
  • points[i].length == 2
  • -2^31 <= xstart < xend <= 2^31 - 1

// 贪心策略:区间问题

代码语言:javascript
复制
class Solution {
    // 本题考查,对重叠区间的处理方式,先遍历,然后设置区间
    static bool cmp(vector<int> p1,vector<int> p2){
        return p1[0]<p2[0]; // 从小到大
    }
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        sort(points.begin(),points.end(),cmp);
        int cnt = 0;
        int l=-1,r=-1; // 左端点,有端点
        for(int i=0; i<points.size(); ++i){
            int cur_l = points[i][0];
            int cur_r = points[i][1];
            if((cur_l>=l&&cur_l<=r)||(cur_r<=r&&cur_r>=l)){
                l = max(cur_l,l);
                r = min(cur_r,r);
            }else{
                cnt++;
                l = cur_l;
                r = cur_r;
            }
        }
        return cnt;
    }
};
十一、无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 注意: 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。 示例 1:

  • 输入: [ [1,2], [2,3], [3,4], [1,3] ]
  • 输出: 1
  • 解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

  • 输入: [ [1,2], [1,2], [1,2] ]
  • 输出: 2
  • 解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

  • 输入: [ [1,2], [2,3] ]
  • 输出: 0
  • 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

// 贪心策略:优先保留结束时间早的区间,为后续区间留出更多空间,从而减少后续重叠的可能性

代码语言:javascript
复制
class Solution {
    // 本题考查,对重叠区间的处理方式,先遍历,然后设置区间
    static bool cmp(vector<int> p1,vector<int> p2){
        return p1[0]<p2[0]; // 从小到大
    }
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        sort(points.begin(),points.end(),cmp);
        int cnt = 0;
        int l=-1,r=-1; // 左端点,有端点
        for(int i=0; i<points.size(); ++i){
            int cur_l = points[i][0];
            int cur_r = points[i][1];
            if((cur_l>=l&&cur_l<=r)||(cur_r<=r&&cur_r>=l)){
                l = max(cur_l,l);
                r = min(cur_r,r);
            }else{
                cnt++;
                l = cur_l;
                r = cur_r;
            }
        }
        return cnt;
    }
};
十二、划分字母区间

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。 示例:

  • 输入:S = "ababcbacadefegdehijhklij"
  • 输出:[9,7,8] 解释: 划分结果为 "ababcbaca", "defegde", "hijhklij"。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

提示:

  • S的长度在[1, 500]之间。
  • S只包含小写字母 'a' 到 'z' 。

// 贪心策略:记录最后出现的值

代码语言:javascript
复制
class Solution {
    // 题目读错,结果就很抽象
public:
    vector<int> partitionLabels(string s) {
        vector<int> pos(s.size(),0);
        vector<int> hash(26,0); // 跟这个无关

        // 记录字符序
        for(int i=s.size()-1; i>=0; --i){ // 字符序
            int ch = s[i]-'a'; // 转化
            hash[ch] = max(hash[ch],i);
            pos[i] = hash[ch];
        }
    
        vector<int> res;
        int cnt = 0;
        int maxn = pos[0];
        for(int i=0; i<s.size(); ++i){ // 开始分割字符
            cnt++; // 个数
            maxn = max(maxn,pos[i]); // 最早能结束的位置
            if(i==maxn){
                res.push_back(cnt); // 存储结果
                cnt = 0; // 置零
            }
        }
        if(cnt!=0) res.push_back(cnt);
        return res;
    }
};
十三、单调递增的数字

给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。 (当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。) 示例 1:

  • 输入: N = 10
  • 输出: 9

示例 2:

  • 输入: N = 1234
  • 输出: 1234

示例 3:

  • 输入: N = 332
  • 输出: 299

说明: N 是在 [0, 10^9] 范围内的一个整数。

// 贪心策略:从右往左,局部调整,之后数字置位9

代码语言:javascript
复制
class Solution {
    // 从右往左寻找递减点,局部调整高位,确保寻找到最大的单调递增数
public:
    int monotoneIncreasingDigits(int n) {
        string str = to_string(n);
        int flag = str.size()+2;
        for(int i=str.size()-1; i>0; --i){
            if(str[i-1]>str[i]){
                flag = i;
                str[i-1]--;
            }
        }
        for(int i=flag; i<str.size(); ++i){
            str[i]='9';
        }
        return stoi(str);
    }
};
十四、监控二叉树

给定一个二叉树,我们在树的节点上安装摄像头。 节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。 计算监控树的所有节点所需的最小摄像头数量。 示例 1:

  • 输入:[0,0,null,0,0]
  • 输出:1
  • 解释:如图所示,一台摄像头足以监控所有节点。

示例 2:

  • 输入:[0,0,null,0,null,0,null,null,0]
  • 输出:2
  • 解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。

提示:

  • 给定树的节点数的范围是 [1, 1000]。
  • 每个节点的值都是 0。

// 贪心策略:递推

代码语言:javascript
复制
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
    // 这道,二叉树,贪心,运用递推公式,运用的非常完美。
public:
    int cnt = 0;
    // 什么都无0 覆盖1 照相机2
    int dfs(TreeNode* root){
        // 覆盖
        if(root == nullptr) return 1; // 表示被覆盖
        
        int l_num = dfs(root->left);
        int r_num = dfs(root->right);
        
        if(l_num==0 || r_num==0){ // 子节点,未被照到
            cnt++;
            return 2; // 放置照相机
        }
        if(l_num==2 || r_num==2){ // 字节点,有照相机
            return 1; // 于是该节点就被覆盖了
        }
        
        return 0; // 表示该节点,没人照顾。
    }               

    int minCameraCover(TreeNode* root) {
        cnt = 0;
        if(dfs(root)==0) cnt++; // 如果根节点,未被覆盖
        return cnt;
    }
};
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、分发饼干
  • 二、摆动序列
  • 三、最大子数组和
  • 四、跳跃游戏
  • 五、跳跃游戏II
  • 六、K次取反后最大化的数组和
  • 七、加油站
  • 八、分发糖果
  • 九、根据身高重建队列
  • 十、用最少数量的箭引爆气球
  • 十一、无重叠区间
  • 十二、划分字母区间
  • 十三、单调递增的数字
  • 十四、监控二叉树
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档