9.贪心算法

2.LeetCode 相关题目

2.1_455 分发饼干

2.1.1 算法描述

先对小孩和饼干数组记性排序,保证可以一个个的判断小孩:

[1,2]饼干:[1,2,3]每次将最小的那一块饼干分给胃口最小的孩子,如果饼干足够则当前小孩满足,如果饼干不够则当前孩子不动,继续向前判断更大的那一块饼干是否满足

单步解决方案:

将当前最小的那块饼干给胃口最小的那个人

2.1.2 C++ 代码实现

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int index = 0;
        for(int i = 0;i < s.size();++i){
            if(index < g.size() && g[index] <= s[i]){
                index++;
            }
        }
        return index;
    }
};

2.1.3 时空复杂度

时间复杂度:O(nlogn)

空间复杂度 😮(1)

2.2_376摆动序列

2.2.1 算法描述

需要注意的点:

①nums 中元素个数的问题:size<=1 时返回元素个数

②最开始的 pre 是等于 0 的,所以在 for 循环的条件中要添加 pre==0 的选项

③记录的是个数,但是数的是区间,所以 res 是从 0 开始的

单步解决方案:

用 判断 pre 和 cur 是否异号,然后再将 cur 赋值给 cur

2.2.2 C++ 代码实现

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.size()<=1)return nums.size(); // 判断 nums 长度
        int cur=0;
        int pre=0;
        int res=1; // 由区间转换为个数所以+1
        for(int i=0;i<nums.size()-1;i++){
            cur = nums[i+1]-nums[i]; // 后面的减前面的
            if((cur>0&&pre<=0)||(cur<0&&pre>=0)){
                pre = cur;
                res+=1;
            }
        }
        return res;

    }
};

2.2.3 时空复杂度

时间复杂度:O(n)

空间复杂度 😮(1)

2.3_53 最大子序和

2.3.1 算法描述

本题没有要求取最大值的区间,只需要最大值,所以只用一个变量 maxVal 记录最大值区间即可

sums 用于记录某个区间的累加和,当这个累加和>max时,就将其记录在 max 中

如果一个值使得 sums<0 则这段子序列应该立刻被丢弃

单步解决方案:

使用 maxVal 记录最大值,并不断参与比对。sums 用于计算当前子序的累加和,如果 sums<0,则这个子序立刻被丢掉

一般使用暴力迭代的方法使用两个变量或者指针就可以解决问题

2.3.2 C++ 代码实现

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int maxVal = INT32_MIN;  // 首先先定义一个非常小的值,C++ 中定义的宏
        int sums = 0;
        for(int i=0;i<nums.size();i++){
            sums+=nums[i];
            if(sums>maxVal){
                maxVal=sums;
            }
            if(sums<=0) sums=0; // 如果加上这个数后 sums ==0 ,则直接将这个数抛弃
        }
        return maxVal;
    }
};

2.3.3 时空复杂度

时间复杂度:O(n)

空间复杂度:O(1)

2.3.4 知识扩展

1.C++ 宏定义最小值

INT_MIN在标准头文件limits.h中定义。

1 #define INT_MAX 2147483647
2 #define INT_MIN (-INT_MAX - 1)

参考文献

2.4_122买卖股票的最佳时机

2.4.1 算法描述

利润分解:

image-20211102153244217

[7,1,5,3,6,4] 买股票真的特别神奇,竟然符合能量守恒定律。可以计算一下:

prices[4]-prices[1] = (prices[4]-prices[3])+ (prices[3]-prices[2])+(prices[2]-prices[1])

也就是将利润进行拆解,将所有利润为正的区间进行相加就可以得到最大利润,管他是如何蜿蜒曲折从 Daym 到 Dayn

单步解决方案:

计算两两区间的差值,如果差值 > 0 ,则将其加入到 sums 中

2.4.2 C++ 代码实现

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int sums=0;
        int tmp = 0;
        for(int i=1;i<prices.size();i++){
            tmp = prices[i]-prices[i-1];
            if(tmp>0) sums+=tmp;
        }
        return sums;
    }
};

2.4.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

2.5_55跳跃游戏

2.5.1 算法描述

首先看这道题需要返回什么,返回的是 true/ false ,也就是说不用去纠结下一步跳到哪,只需要判断每个元素的覆盖范围是否可以覆盖到最后即可

image-20211102161200162

单步解决方案:

依次判断每个元素的 cover 是否可以覆盖到最后

2.5.2 C++ 代码实现

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int cover = 0;
        if(nums.size()==1) return true; // 只有一个元素则可以直接到达终点
        for(int i=0;i<=cover;i++){ // 小于等于 cover ,因为这是下一条能够跳到的范围
            cover = max(i+nums[i],cover);
            if(cover>=nums.size()-1) return true; // cover 包含住的是 index ,下一步可以跳的 index 
        }
        return false;

    }
};

易错点:

cover 停留在最后一个元素也算是 true,所以是 if(cover>=nums.size()-1)

2.5.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

2.6_45跳跃游戏2

2.6.1 算法描述

单步解决方案:

这里需要进行两个判断:

①预测 i 的下一跳谁能跳到最远距离

②i 跳到最远距离,与此同时判断下一次往哪跳

2.6.2 C++ 代码实现

class Solution {
public:
    int jump(vector<int>& nums) {
        int curMax = 0; // 当前覆盖距离最远的下标
        int steps = 0; // 步数
        int nextMax = 0; // 下一条覆盖的最远下标
        for(int i =0;i<nums.size()-1;i++){ // 因为总是可以到达最后的位置,所以判断到倒数第二个格子就好了
            nextMax = max(nums[i]+i,nextMax);  // 预测下一跳谁能跳到最远距离
            if(i==curMax){ // i 走到了最远距离的下标
                    curMax = nextMax; // i 跳过去,与此同时又要重新判断一波最短距离
                    steps++;
                }
        }  
        return steps;
    }
};

2.6.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

2.7_ 1005.K次取反后最大化的数组和

2.7.1 算法描述

单步解决方案:

先将所有的负数变为正数,剩余的 K 对绝对值小的数不断变号

2.7.2 C++ 代码实现

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);       // 根据绝对值排序
        for (int i = 0; i < nums.size(); i++) { // 添加负号
            if (nums[i] < 0 && k > 0) {
                nums[i] *= -1;
                k--;
            }
        }
        if (k % 2 == 1) nums[nums.size() - 1] *= -1; // 判断剩余 K 的大小
        int sums = 0;
        for (int n : nums) sums += n;        // 处理剩余 k 
        return sums;
    }
};

2.7.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

2.7.4 知识扩展

如何根据绝对值定义比较规则

static bool cmp(int a,int b){
    return abs(a)>abs(b);
}

2.8_134加油站

如果总油量减去总消耗大于等于零那么一定可以跑完一圈,反之则不行。

②判断从哪一个加油站开始

在已知可以顺利走下来的情况下不断的循环判断,i 在当前位置时汽车的存油 curSum 是否可以到达下一个加油站行:继续在 curSum 的值上累加,判断下一个加油站 i+1不行:curSum =0 ,将 i+1 作为新的出发点

单步解决方案:

每一步都去判断当前剩余的油是否可以到达下一个加油站

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum=0; // 当前剩余的油
        int totalSum=0; // 总共的油
        int start = 0; // 记录从哪个 station 开始
        for(int i =0;i<gas.size();i++){
            curSum+=gas[i]-cost[i]; // 如果想从这个加油站去到下一个加油站剩余的油会是多少
            totalSum+=gas[i]-cost[i]; // total 的值后期不会再发生变动,所以它记录的是是否会到达终点
            if(curSum<0){ //剩余的油<0
                start = i+1;
                curSum = 0;
            }
        }
        if(totalSum<0) return -1; // 无法跑完一圈
        return start;
    }
};

2.9_135分发糖果

2.9.1 算法描述

从左往右推:

从左边的第一个孩子开始逐渐向右推,判断每个孩子至少需要几个糖。这样第一个孩子初始化是 1 最小值即可。这样的话从左向右的条件是满足的

从右向左推:

从最右边的孩子开始往前推,推每一个孩子至少需要几个糖果。这样最后一个孩子的糖果是最小值 1 。因为是从右向左推,所以这个方向是满足的

合并:从左往右和从右往左取最大值,那就是同时满足的

单步解决方案:

从左向右为例

cur > 其左边:cur.糖果=左边糖果+1

cur <=左边:cur.糖果 = 1

2.9.2 C++ 代码实现

class Solution {
public:
    int candy(vector<int>& ratings) {
        vector<int> res(ratings.size(),1); // 只是用一个 vector 即可
        // 1. 先从左向右进行判断
        for(int i =1;i<ratings.size();i++){
            if(ratings[i]>ratings[i-1]) res[i] = res[i-1]+1; 
            // else if(ratings[i]<=ratings[i-1]) res[i] = 1; <= 的情况可以直接不用写
        }
        // 2.从右向左判断
        for(int i=ratings.size()-2;i>=0;i--){
            if(ratings[i]>ratings[i+1]) res[i] = max(res[i],res[i+1]+1); // 这一步的代码可以简化
        }
        //3.累加糖果
        int sums = 0;
        for(int i=0;i<res.size();i++){
            sums+= res[i];
        }
        return sums;
    }
};

2.9.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N)

2.10_860柠檬水找零

2.10.1 算法描述

单步解决方案:

遇到 20 先花 10,没有 10 再用 5 凑

因为这里的金额比较少,所以可以不用 Map 形成钱和个数的映射,直接使用变量即可

2.10.2 C++ 代码实现

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int five = 0;
        int ten = 0;
        for(int i =0;i<bills.size();i++){
            if(bills[i]==5) five++; // 5
            if(bills[i]==10){ // 10
                if(five>0){ 
                    five--;
                    ten++;
                }
                else return false; 
            }
            if(bills[i]==20){ //20
                if(ten>0&&five>0){
                    ten--;
                    five--;
                }
                else if (five>=3){
                    five-=3;
                }
                else return false;
            }
            }
            return true;
        }
};

2.10.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

2.11_406根据身高重建队列

2.11.1 算法描述

如何进行排序:

不容易受到影响的人:身高高的+容纳性更好的(第二维大的)

根据身高进行排序,如果身高相同则容纳性好的放在后面

然后对排好序的 vector 进行处理

为什么按照上面方法排序的原因:

我们先排的是身高高的元素,因为这些元素在 insert 后面向后移动的时候不容易受到影响。当 cur 放在其本应该去的位置上时,如果那个位置之前被安排过,那么安排的元素也是身高比 cur 大的元素,所以将这些元素向后移是不会产生影响的。

为什么使用 list 不使用 vector:

但使用vector是非常费时的,C++中vector(可以理解是一个动态数组,底层是普通数组实现的)如果插入元素大于预先普通数组大小,vector底部会有一个扩容的操作,即申请两倍于原先普通数组的大小,然后把数据拷贝到另一个更大的数组上。

所以使用vector(动态数组)来insert,是费时的,插入再拷贝的话,单纯一个插入的操作就是O(n^ 2)了,甚至可能拷贝好几次,就不止O(n^2)了。

单步解决方案:

将 cur 根据容纳量放在适当的位置,这个位置及其之前的位置一定是满足 cur 的,那么就放在满足的临界点。

每一次将 cur insert 到本应该去的位置

2.12.2 C++ 代码实现

1.vector 作为底层实现

class Solution {
public:
    static bool cmp(const vector<int>a,const vector<int>b){
        if(a[0]==b[0]) return a[1]<b[1];
        return a[0]>b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        vector<vector<int>>que; // 
        for(int i =0;i<people.size();i++){
            int position = people[i][1]; // 获得 cur 应该插入的位置
            que.insert(que.begin()+position,people[i]); // 插入好 cur ,它后面的元素会进行后移
        }
        return que;
    }
};

2.list 作为底层实现

class Solution {
public:
    // 排序算法
    static bool cmp(vector<int> a, vector<int>b){
        if(a[0]==b[0]) return a[1]<b[1];
        return a[0]>b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        list<vector<int>> res; // 底层使用链表实现
        for(int i =0;i<people.size();i++){
            int position = people[i][1]; // 插入到 position 的位置
            std::list<vector<int>>::iterator it = res.begin(); // 这里要添加标准库
            while(position--) it++; // 通过对迭代器 ++ 移动迭代器
            res.insert(it,people[i]); // list 不可以直接使用 .begin()+position
        }
        return vector<vector<int>>(res.begin(),res.end()); // 最后返回的时候要返回 vector
    }
};

这里对 list 的处理需要注意:

1.在 insert 的时候不可以使用 res.begin() 的方式移动迭代器

要使用一个 while 循环,不断的更改迭代器的位置

在 insert 的时候直接将迭代器传入

2.函数最后返回的时候返回的是 vector

需要将 list 的迭代器传入 vector

2.12.3 时空复杂度

时间复杂度:

空间复杂度:

2.12.4 知识扩展

1.vector inser 的使用

image-20211103164927816

参考连接

2.sort 的使用

参考资料

一共接收三个参数,第一二个参数是迭代器。第三个参数是排序的方法,返回一个 bool 类型

image-20211102213038225

第三个参数用于定义比较的规则

情况①:对于一维 vector 进行比较

假设需要排序的数组为,vector< int > ,cmp 参数接收的是其降一维的元素,也就是说参数传入的是 int

最后 return a>b 就是由小到大

情况②:对于二维 vector 进行比较

假设需要排序的数组为,vector< vector< < int > >,cmp 参数接收的是其降一维的元素,也就是说参数传入的是 vector< int>

最后 return a[0]>b[0] j就是根据第0 维进行排序,由小到大

2.13_452用数量最少的箭引爆气球

2.13.1 算法描述

image-20211103201940008

单步解决方案:

判断当前坐标是否可以囊括到前面最小射的范围中

最小射的范围随着后面囊括进来的坐标范围而变化,取那个范围小的

超时问题:

注意这里最好是在原地操作,如果每次定义两个最小边界坐标 left 和 right 用来表示最小范围会导致超时现象

2.13.2 C++ 代码实现

class Solution {
private:
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        return a[0] < b[0];
    }
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if (points.size() == 0) return 0;
        sort(points.begin(), points.end(), cmp);

        int result = 1; // points 不为空至少需要一支箭
        for (int i = 1; i < points.size(); i++) {
            if (points[i][0] > points[i - 1][1]) {  // 气球i和气球i-1不挨着,注意这里不是>=
                result++; // 需要一支箭
            }
            else {  // 气球i和气球i-1挨着
                points[i][1] = min(points[i - 1][1], points[i][1]); // 更新重叠气球最小右边界
            }
        }
        return result;
    }
};

2.13.3 时空复杂度

时间复杂度:O(nlogn) 因为有一个快排,快排时间复杂度 O(nlogn)

空间复杂度:O(1)

2.14_435无重叠区间

2.14.1 算法描述

首先这个题像射箭那个题,先对 vector 进行排序,当发生重叠时 比如 [1,2] 和 [1,3] 将末尾短的那个元素保留,因为末尾长的很可能又和后面的发生重叠

单步解决方案:

判断是否发生重叠

如果发生重叠判断去掉哪个,然后更新区间

2.14.2 C++ 代码实现

class Solution {
public:
    static bool cmp(vector<int>& a,vector<int>& b){
        if(a[0]==b[0]) return a[1]<b[1];
        else{
            return a[0]<b[0];
        }
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        int res=0;
        sort(intervals.begin(),intervals.end(),cmp);
        for(int i =1;i<intervals.size();i++){
            if(intervals[i-1][1]>intervals[i][0]){ // 产生了重合
                res++;
                intervals[i][1] = min(intervals[i-1][1],intervals[i][1]);
            }
        }
        return res;
    }
};

2.14.3 时空复杂度

时间复杂度:O(nlogn ) 因为有快排

空间复杂度:O(1)

2.15_763划分字母区间

2.15.1 算法描述

先将所有字母的最远距离进行记录

从头开始遍历数组

image-20211104133312237

单步解决方案:

如果 cur 到达了区间内最远的距离则进行记录,同时开始下一次区间判断

不断记录区间内的最大距离

2.15.2 C++ 代码实现

class Solution {
public:
    vector<int> partitionLabels(string s) {
        int hask[27] = {0}; // 用于记录每个字母出现的最远位置
        int maxInd = 0; // 用于保存区间内的最大下标
        int left=0; // 用于保存下一个区间的左边界
        vector<int> res;
        // 保存最远位置
        for(int i =0;i<s.size();i++)
            hask[s[i]-'a'] = i;
        // 遍历每一个元素,如果到达最远位置就截取
        for(int i =0;i<s.size();i++){
            maxInd = max(maxInd,hask[s[i]-'a']);
            if(i==maxInd){ // 到达了最远下标位置
                res.push_back(maxInd-left+1);
                left = i+1;
            } 
        }
        return res;
    }
};

2.15.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

用于记录字母的区间是常量

2.16_56合并区间

2.16.1 算法描述

单步解决方案:

先进行排序,然后再合并结果,将合并的结果保存到 res 中

2.16.2 C++ 代码实现

class Solution {
public:
    static bool cmp(vector<int>& a,vector<int>& b){
        return a[0]<b[0];
    }
    vector<vector<int>> res;
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end(),cmp);
        res.push_back(intervals[0]);
        for(int i =1;i<intervals.size();i++){
            int length = res.size();
            if(res[length-1][1]>=intervals[i][0]){ // 合并
                res[length-1][1] = max(intervals[i][1],res[length-1][1]);
            }
            else{
                res.push_back(intervals[i]);
            }
        }
        return res;
    }
};

2.16.3 时空复杂度

时间复杂度:O(nlogn) 有排序快排

image-20211106190527271

空间复杂度:O(1) 平均空间复杂度

2.17_738单调递增的数字

2.17.1 算法描述

在两个数的情况下 76 ,6<7 ,则 6 变为 9 ,7 变为 6 是最大值。任何两位数都是这么判断

从后向前每两个数按照上面的方法进行判断

单步解决方案:

如果 nums[i]<nums[i-1] ,nums[i] = 9,nums[i-1]=nums[i-1]-1

image-20211104150520313

易错点:虽然后面的满足递减,当前面有两个数不满足时不满足的位置上的那个数以及其后面的数全部设为 9

2.17.2C++代码实现

class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        string strNum = to_string(n); // 将 n 变为 string 类型
        int flag = strNum.size(); // 设置标志位,其后面的数全部变为 9 
        for(int i = strNum.size()-1;i>0;i--){
            if(strNum[i-1]>strNum[i]){
                strNum[i-1]--;
                flag = i; // i 的这个位置本来要变为 9 的,现在先标记一下
            }
        }
        for(int i = flag;i<strNum.size();i++) strNum[i] = '9'; // 设为 9 
        return stoi(strNum);
    }
};

2.17.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N) 需要一个字符串将 n 转化为 str

2.17.4 知识扩展

1.将 str 类型转换为 int

使用函数 stoi 进行转换

stoi(strNum)

2.对于 str 的运算

可以直接在 string 中进行四则运算,运算的是 ASCII 码

2.18_714买卖股票的最佳时机含手续费

2.18.1 算法描述

因为要收手续费,所以不是一获利就卖,即使获利也要判断卖还是不卖

那么当天的交易一共分成三种情况

1.当天买入:当前价格 < 扫描过来的最小价格 min_price

遇到更低的点就进行记录,但是先不操作,直到遇到合适的卖出点一起进行操作

2.当天不进行任何操作

没有利润可以赚:卖出的价格<=当前买入最小价格+手续费

3.当天卖出

有利润可以赚:卖出价格>当前买入最小价格+手续费

这一天需要进行以下操作:

①对盈利的价格进行叠加运算

②一个完整交易已经结束,重新记录最低价格

2.18.2 C++ 代码实现

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int res=0;
        int minPrice = prices[0]; // 记录当前最小价格
        for(int i=1;i<prices.size();i++){
            // 情况1 :判断当前是否是最小价格
            if(prices[i]<minPrice) minPrice = prices[i];
            // 情况2 :不进行任何操作
            if(prices[i]<(minPrice+fee)){
                continue;
            }
            // 情况3:卖出时的操作
            if(prices[i]>(minPrice+fee)){
                res+=prices[i]-(minPrice+fee); // 累加利润
                minPrice = prices[i]-fee; // 这里的最小价格不是 prices 中的某个值
            }
        }
        return res;

    }
};

2.18.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

2.19_968 监控二叉树

2.19.1 算法描述

遍历树的方式:

从下向上判断,首先叶子节点最好是不放摄像头的,所以最好先自主的对底层的节点进行控制。如果是由上面推下面就很难控制底层叶子节点是否放摄像头了。所以选择后续遍历,通过后续遍历 return 的结果判断 cur 的状态应该是什么

对于每个节点的判断:

根据底层 return 的结果 cur 一共分为三种情况:

0.cur 有覆盖

1.cur 有摄像头

2.cur 无覆盖

根据以上三种情况判断如何写 return

cur 有覆盖:左右子树至少有一个摄像头

cur有摄像头:①左右都没有覆盖②左有覆盖,右没有覆盖③左没有覆盖,右有覆盖④左节点有摄像头,右节点没覆盖⑤右节点有摄像头,左节点没覆盖。

所有左右两边有一边是没有覆盖的父节点就要装摄像头,不管另一边是个啥

cur无覆盖:左右子树都有覆盖

对于空节点的判断:

一般叶子节点的孩子都是空节点,空节点放摄像头是不值得的。如果空节点有覆盖的话肯定是叶子节点的摄像头给它的,但是叶子节点又不放摄像头所以空节点不能有覆盖,所以空节点是无覆盖, 空节点 return 2。

对根节点的判断

最后函数 return 的值是 根节点的结果,别忘了对根节点进行判断:

2.19.2 C++ 代码实现

/**
 * 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 {
private:
    int result;
    int traversal(TreeNode* cur) {

        // 空节点,该节点有覆盖
        if (cur == NULL) return 2;

        int left = traversal(cur->left);    // 左
        int right = traversal(cur->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) {
            result++;
            return 1;
        }

        // 情况3
        // left == 1 && right == 2 左节点有摄像头,右节点有覆盖
        // left == 2 && right == 1 左节点有覆盖,右节点有摄像头
        // left == 1 && right == 1 左右节点都有摄像头
        // 其他情况前段代码均已覆盖
        if (left == 1 || right == 1) return 2;

        // 以上代码我没有使用else,主要是为了把各个分支条件展现出来,这样代码有助于读者理解
        // 这个 return -1 逻辑不会走到这里。
        return -1;
    }

public:
    int minCameraCover(TreeNode* root) {
        result = 0;
        // 情况4
        if (traversal(root) == 0) { // root 无覆盖
            result++;
        }
        return result;
    }
};

2.19.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

3.其他题目

3.1_12整数转罗马数字

LeetCode 题目链接

3.1.1 算法描述

使用贪心算法,每次先判断最大的那个罗马字符是否可以代替,比如 3000 ,使用 1000 是否可以代替。但是判断 600 时发现 500 不能代替,所以再向下找到 100 。因为 4,9 比较特殊特殊,所以这里直接将 4,9 也枚举了出来

局部最优解:每次判断最大的那个罗马数字是否可以进行取代

对于这种特殊情况不多时可以将所有的特殊情况进行枚举

3.1.2 代码实现

class Solution {
  public:
  string intToRoman(int num) {
    // 这里的映射一定要从大到小按顺序存储
    int values[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
    string reps[] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
    string res;
    int i = 0; // 指向 values 和 reps 
    while(num>0){
      while(num>=values[i]){
        num-= values[i];
        res+= reps[i];
      }
      i++;
    }
    return res;
  }
};

3.1.3 时空复杂度

时间复杂度:O(1) 循环次数是有上限的

空间复杂度:O(1) 因为存放罗马数字的数组大小是固定的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值