代码随想录:贪心算法

455.分发饼干

自解代码如下:

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int res=0;
        int index=s.size()-1;
        if(s.size() == 0) return res;
        //先遍历胃口,再遍历饼干
        for(int i=g.size()-1; i>=0; i--){
            //int sum=0;
            for(int j=index; j>=0; j--){
                //一个饼干即可满足
                //sum+=s[j];
                if(s[j] >= g[i]){
                    res++;
                    index=j-1;
                    break;
                }
            }
        }
        return res;
    }
};

刚开始整体代码包括注释部分,但是测试用例没有全部通过,经过修改发现是自己的理解出了问题,自己的理解是认为一个人可以得到多块饼干,但最后发现一个孩子只能得到一块饼干。根据贪心策略,应该尽可能用大饼干去满足胃口大的孩子,故先对两个数组进行排序,从大到小开始喂。看了Carl哥的答案发现代码可以继续优化,只使用一个for循环即可完成,代码如下:

//仅用一个for循环
class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int res=0;
        int index=s.size()-1;
        if(s.size() == 0) return res;
        //先遍历胃口,再遍历饼干
        for(int i=g.size()-1; i>=0; i--){
            if(index>=0 && s[index]>=g[i]){
                res++;
                index--;
            }
        }
        return res;
    }
};

376.摆动序列*(贪心或动态)

卡哥代码如下:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.size() == 1) return 1;
        int pre=0; //前一对差值
        int cur=0; //当前一对差值
        int res=1; // 记录峰值个数,默认序列最右边有一个峰值
        for(int i=0; i<nums.size()-1; i++){
            cur=nums[i+1]-nums[i];
            if((pre<=0&&cur>0) || (pre>=0&&cur<0)){
                res++;
                pre=cur;
            }
        }
        return res;
    }
};

本题中三大坑点,需要思考:
1.上下坡中有平坡;
2.数组首尾两端;
3.单调坡中有平坡。
将pre=cur放在if中就是为了排除第三种情况。
在讨论区又看到了一种神仙解法,本质应该是动态规划,代码如下:

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int n = nums.length;
        if (n < 2) {
            return n;
        }
        int up = 1;
        int down = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] > nums[i - 1]) {
                up = down + 1;
            }
            if (nums[i] < nums[i - 1]) {
                down = up + 1;
            }
        }
        return Math.max(up, down);
    }
}

53.最大子数组和

代码如下:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int res=INT32_MIN; //返回最大和
        if(nums.size() == 1){
            return nums[0];
        }
        int count=0;
        for(int i=0; i<nums.size(); i++){
            count+=nums[i];
            //res=res<count?count:res;
            if(res < count){
                res=count;
            }
            if(count <= 0){
                count=0;
            }
        }

        return res;
    }
};

面对这道题,我们首先可能会想到有暴力搜索的方法解决,即需要两个for循环,但是很有可能会遇到超时的问题。上面的方法则是在 O ( n ) O(n) O(n)的时间复杂度进行解决。当我们求累加和的时候,当加上下一个数时,和的值为负数,我们就可以考虑将起始位置延后了。另一个需要注意的地方是,对res进行初始赋值的时候,我们设置了res=INT32_MIN,因为当参数数组里的元素全为-1时候,我们给res设置成0的话,返回的结果仍为0,这个结果是不对的。

122.买卖股票的最佳时机ii(可贪心可动态)

代码如下:

class Solution {
public:
    //可动态规划,可贪心
    //计算每隔一天的利润,最后累加利润只为正数的结果,即是最大利润
    int maxProfit(vector<int>& prices) {
        int res=0;
        for(int i=1; i<prices.size(); i++){
            res+=max(prices[i]-prices[i-1],0);
        }
        return res;
    }
};

在这里插入图片描述
局部最优为每天的正利润,通过正利润推出全局最优。 局部最优可以推出全局最优,找不出反例,试一试贪心!

55.跳跃游戏

代码如下:

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int cover=0;
        //注意是<=cover
        //每次都取最大覆盖范围,每移动一个单位则更新最大覆盖范围
        //局部最优:每次取最大跳跃步数(即最大覆盖范围) 整体最优:得到整体最大覆盖范围
        for(int i=0; i<=cover; i++){
            cover=max(i+nums[i], cover);
            if(cover >= nums.size()-1){
                return true;
            }
        }
        return false;
    }
};

45.跳跃游戏ii(**)

代码如下:

class Solution {
public:
    int jump(vector<int>& nums) {
        int res=0;
        int nextdistance=0;
        int curdistance=0;
        for(int i=0; i<nums.size()-1; i++){
            nextdistance=max(i+nums[i], nextdistance);//下一步最大覆盖距离
            if(i == curdistance){ //当前移动下标等于当前最大覆盖距离
                curdistance=nextdistance;
                res++;
            }
        }
        return res;
    }       
};

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1005.K次取反后最大化的数组和(一道题中两次贪心的思想)

代码如下:

class Solution {
static bool cmp(int a, int b){
    return abs(a)>abs(b);
}
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        int res=0;
        //按照绝对值的的大小, 从大到小排序
        sort(nums.begin(), nums.end(), cmp);
        for(int i=0; i<nums.size(); i++){
            if(k>0 && nums[i]<0){
                nums[i] *= -1;
                k--;
            }
        }
        //如果剩余k值为奇数则转变
        if(k % 2==1){
            nums[nums.size()-1] *= -1;
        }
        for(auto i:nums){
            res+=i;
        }
        return res;
    }
};

第一步:按照绝对值的大小,从大到小排序;
第二步:从前向后遍历,遇到负数将其变正,同时k–;
第三步:如果k值仍大于0,将数组中最小的元素,反复转变
第四步:求和

134加油站

暴力法代码如下(容易运行超市AC不了),应注意while的使用,环形循环结构应考虑使用while

//暴力法容易超时
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        for(int i=0; i<cost.size(); i++){
            int leave=gas[i] - cost[i];//记录剩余油量
            int index=(i+1)%cost.size();
            //如果leave>=0 则答案不唯一了
            while(leave>0 && index!=i){
                leave+=gas[index]-cost[index];
                index=(index+1)%cost.size();
            }
            //如果以i为起点跑一圈,剩余油量>=0,返回该起始位置i
            if(leave>=0 && index==i){
                return i;
            }
        }
        return -1;
    }
};

以上代码中,应注意index的使用,同时也应注意%的特性,一个数%另一个数,如a%b,则得值永远不会大于b,在0-(b-1)区间。

第二种方法:贪心代码如下:

//贪心
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int start=0;
        int curnum=0;
        int totalnum=0;
        for(int i=0; i<cost.size(); i++){
            curnum+=gas[i]-cost[i];
            totalnum+=gas[i]-cost[i];
            if(curnum<0){
                start=i+1;
                curnum=0;
            }
        }
        if(totalnum<0) return -1;
        return start;
    }
};

可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。每个加油站的剩余量rest[i]为gas[i] - cost[i]。i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

135.分发糖果(hard)

代码如下:

class Solution {
public:
    int candy(vector<int>& ratings) {
        vector<int> sweet(ratings.size(),1);
        //从前向后,确定右孩子
        for(int i=1; i<ratings.size(); i++){
            if(ratings[i]>ratings[i-1]){
                sweet[i]=sweet[i-1]+1;
            }
        }

        //从后向前,确定左孩子
        for(int i=ratings.size()-2; i>=0; i--){
            if(ratings[i]>ratings[i+1]){
                sweet[i]=max(sweet[i],sweet[i+1]+1);
            }
        }
        int res=0;
        for(int i:sweet){
            res+=i;
        }
        return res;
    }
};

本题难点:
需要进行两次遍历进行贪心选择,第一次从左向右遍历,确定右孩子分得的糖果数;第二次从右像左遍历确定左孩子的糖果数,如果评分出现左孩子大于右孩子的情况,则sweet[i]=max(sweet[i],sweet[i+1]+1),确保最后确定的糖果数即大于它自己的左边,也大于他自己的右边。

860.柠檬水找零(自解)

本题虽然是easy,但是是我自己解决的思路和Carl哥一样,有点开心。代码如下:

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        //sort(bills.begin(), bills.end());
        int num5=0;
        int num10=0;
        int num20=0;
        for(int i=0; i<bills.size(); i++){
            if(bills[i] == 5){
                num5++;
            }
            if(bills[i] == 10){
                if(num5<=0) return false;
                num10++;
                num5--;
            }
            if(bills[i] == 20){
                if(num10>0 && num5>0){
                    num20++;
                    num10--;
                    num5--;
                }
                else if(num10==0 && num5>0){
                    num5-=3;
                    if(num5<0){
                        return false;
                    }
                    num20++;
                }
                else{
                    return false;
                }

            }
        }
        return true;
    }
};

本题的贪心思想就是给20元找零时,有10元则优先用10元,没10元再用5元,因为5元是“万能”的。

406.根据身高重建队列(vector底层扩容原理)

代码如下,本题注意使用list时间效率将会提高,同时应注意vector底层扩容原理,导致实际时间复杂度要高的多,所以考虑使用底层实现原理为链表的list。

//注意使用list<vector<int>> 时间效率要比使用二维vector高的多
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; //list 底层实现是链表
        for(int i=0; i<people.size(); i++){
            int position=people[i][1];
            list<vector<int>>::iterator it=res.begin();
            while(position--){
                it++;
            }
            res.insert(it,people[i]);
        }

        return vector<vector<int>>(res.begin(), res.end());
    }
};

本题思路是,应先按照身高大小,进行从大到小排列,再跟据k值进行插入。
在这里插入图片描述

452.用最少数量的箭引爆气球

代码如下:

class Solution {
public:
    //参数用引用,用形参会超时
    static bool cmp(vector<int>& a, vector<int>& b){
        return a[0]<b[0];
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        if(points.size() == 0){
            return 0;
        }
        sort(points.begin(), points.end(), cmp);
        int res=1;
        for(int i=1; i<points.size(); i++){
            //不重叠 箭+1
            if(points[i][0] > points[i-1][1]){
                res++;
            }
            //重叠 更新最小重叠右边界
            //如果气球重叠了,重叠气球中右边边界的最小值之前的区间一定需要一个弓箭。
            else{
                points[i][1]=min(points[i-1][1],points[i][1]);
            }
        }
        return res;
    }
};

在这里插入图片描述

435.无重叠区间

本题其实和452.用最少数量的箭引爆气球非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。
但一开始我做这道题却没有考虑到还可以这样,我的思路是按vector[0]大小,从小到大排列,即从左到右排列,有重叠区间即更新end(也就是下次循环需跟他比较大小),代码如下:

class Solution {
public:
    static bool cmp(vector<int>& a, vector<int>& b){
        return a[0]<b[0];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if(intervals.size() == 1) return 0;
        sort(intervals.begin(), intervals.end(), cmp);
        int res=0;
        //只判断重叠情况即可
        for(int i=1; i<intervals.size(); i++){
            //重叠情况
            if(intervals[i][0] < intervals[i-1][1]){
                intervals[i][1]=min(intervals[i][1],intervals[i-1][1]);
                res++;
            }
        }
        return res;
    }
};

763.划分字母区间

代码如下:

class Solution {
public:
    vector<int> partitionLabels(string s) {
        int hash[27]={0};
        //记录每个英文字母最后出现的位置
        for(int i=0; i<s.size(); i++){
            hash[s[i]-'a']=i;
        }
        vector<int> res;
        int right=0;
        int left=0;
        for(int i=0; i<s.size(); i++){
            right=max(right,hash[s[i]-'a']); //找到字符出现的最远边界
            if(i==right){
                res.push_back(right-left+1);
                left=i+1;
            }
        }
        return res;
    }
};

在这里插入图片描述

56.合并区间

代码如下:

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) {
        vector<vector<int>> res;
        sort(intervals.begin(), intervals.end(), cmp);

        res.push_back(intervals[0]);
        for(int i=1; i<intervals.size(); i++){
            //有重叠
            if(res.back()[1]>=intervals[i][0]){
                res.back()[1]=max(res.back()[1],intervals[i][1]);
            }
            else{
                res.push_back(intervals[i]);
            }
        }
        return res;
    }
};

在这里插入图片描述

738.单调递增的数字

代码如下:

class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        string str=to_string(n);
        int flag=str.size();
        for(int i=str.size()-1; i>=1; i--){
            if(str[i-1]>str[i]){
                str[i-1]--;
                flag=i;
            }
        }
        for(int i=flag; i<str.size(); i++){
            str[i]='9';
        }

        return stoi(str);
    }
};

注意对flg初始化的时候,不要让flag=0,因为这样遇到n=1234的情况,得到的答案会变为9999。本题的思路就是从后向前遍历,当遇到前一位大于当前位的时候,让前一位减减,当前位变位9,例如当n=98时,9>8,则9变为8,当前位的8变为9,输出答案89。

968.监控二叉树

代码如下:

class Solution {
private:
    //0:无覆盖
    //1:本节点有摄像头
    //2:本节点有覆盖
    int res=0;
    int travesal(TreeNode* root){
        if(root == NULL){
            return 2;
        }

        int left=travesal(root->left);
        int right=travesal(root->right);

        //情况1 左右孩子都有覆盖 本节点则为0 
        if(left==2 && right==2){
            return 0;
        }
        //3:左右孩子至少有一个无覆盖
        if(left==0 || right==0){
            res++;
            return 1; 
        }
        //2:左右孩子至少有一个摄像头
        if(left==1 || right==1){
            return 2;
        }

        return -1;
    }

public:
  
    int minCameraCover(TreeNode* root) {
        res=0;
        //需要判断头节点是不是无覆盖的情况
        if(travesal(root) == 0){
            res++;
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值