LeedCode刷题笔记

本文介绍了贪心算法在多个编程问题中的应用,包括分饼干、分糖果、不重叠区间、种花和用最少数量的箭引爆气球等。每个问题都详细分析了解题思路和关键步骤,例如按结束时间排序、优先满足条件等,并展示了如何通过双指针、遍历等方法实现。此外,还提到了在实现过程中需要注意的细节,如排序函数的使用和区间判断条件等。
摘要由CSDN通过智能技术生成

1 贪心算法

1.1 分饼干(455)

本题比较简单,贪心策略是优先满足胃口较小的孩子,这样就可以满足尽量多的孩子。思路是:先将两数组排序,同时遍历两数组即可得到最大可以满足多少个孩子。

int removeDuplicates(vector<int>& g, vector<int>& s) {
        //先对g,s数组排序
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        
        //根据贪心算法优先满足胃口小的海子,双指针遍历两个数组
        int i=0, j=0, sum=0;
        while (i < g.size() && j < s.size()) {
            if (g[i] <= s[j]) {
                sum++;
                i++; 
                j++;
            }
            else {
                j++;
            }
        }
        return sum;
    }

注意sort函数的用法。

1.2 分糖果(135)

这一题的难度较大,难点在于理清楚逻辑。在做题过程中,我犯了两次逻辑错误,

(1)我想当第i个节点大于两边节点(至少一边严格大于)时,就要多给i节点一个糖果,遍历一次就行。这个错误在于对:[1,3,2,1]这样的测试用例,3大于2和1,多分一个糖果,但是由于2>1,所以2还得多分一个糖果,而之前的3并没有相应的+1;

所以正确的理解应该是:

我们可以将「相邻的孩子中,评分高的孩子必须获得更多的糖果」这句话拆分为两个规则,分别处理。

左规则:当 \textit{ratings}[i - 1] < \textit{ratings}[i]ratings[i−1]

右规则:当 \textit{ratings}[i] > \textit{ratings}[i + 1]ratings[i]>ratings[i+1] 时,ii 号学生的糖果数量将比 i + 1i+1 号孩子的糖果数量多

但在这里我又犯了一个错误:

(2)以为第2遍遍历和第一遍一样即可,但满足右规则,应从右向左遍历,即为了解决[1,3,2,1]中3没有更新这样的问题,同时判断条件也要修改。

 int i,len=ratings.size();
        vector<int> candy(len, 1);//初始化长度为len,值全为1的数组
        for (i = 0; i < len-1 ; i++) {
            if (ratings[i] < ratings[i + 1]) {
                candy[i+1] = candy[i] + 1;
            }
        }
        for (i = len-1; i >0; i--) {
            if (ratings[i] < ratings[i - 1]) {
              candy[i-1]=max(candy[i-1],candy[i]+1);
              //这里之所以用max,是因为两轮遍历,中间存在大量重复比较,如果后面没有修改,就不
//需要+1。如【1,3,2,2】,【1,3,2,1】中的3就分别对应max的两种情况
            }
        }
        
        return accumulate(candy.begin(),candy.end(),0);
        //0为求和初值

本题还教会我:(1)初始化vector向量 (2)accumulate求和,注意头文件

1.3 不重叠区间(435)

本题实际上是算法课上介绍过的最大兼容子活动问题,每个活动的持续时间对应与一个区间。思路是:

(1)先将活动按结束时间先后排序

(2)根据算法,优先选择活动结束早的,且与当前活动集合兼容的活动

  int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        //先将区间按结束时间排序
        sort(intervals.begin(), intervals.end(), cmp);

        //贪心策略:优先选择结束时间早的活动
        int size = intervals.size(), i,num=0; 
        int k = intervals[0][1];//k初值为第一项活动结束时间
        for (i = 1; i < size; i++) {
            if (k <= intervals[i][0]) {
                //下一项活动可以兼容,k修改为该活动结束时间
                k = intervals[i][1];
            }
            else {
                //该活动不可兼容
                num++;
            }
        }
        return num;
    }
    //cmp函数应写成静态成员函数
    static bool cmp(const vector<int>& x, const vector<int>& y) {
        //按结束时间递归排序区间
        if (x[1] != y[1]) return x[1] < y[1];
        else {
            return x[0] < y[0];
        }
    }

语法收获:(1)复习使用sort函数,注意这里的cmp函数应该写成静态成员函数,类似于全局函数,不然直接使用会出错;

(2)sort函数也可以这样用,直接把比较函数跟在后面。

sort(intervals.begin(), intervals.end(), [](const auto& u, const auto& v) {
            return u[1] < v[1];
        });

1.4 种花(605)

本题比较简单,使用贪心策略,从头开始遍历数组,能种花的位置优先种上,最后即可得到结果。开始时考虑头尾情况使程序分支太多。于是在原数组首尾加上零元素,即可全部当做一般结点统一处理了。

bool canPlaceFlowers(vector<int>& flowerbed, int n) {
         int i, size = flowerbed.size();
        //先在首尾各插入一位,避免特殊处理头尾
        flowerbed.push_back(0);
        flowerbed.insert(flowerbed.begin(), 0);

        
         for (i = 1; i <= size; i++) {
            if (flowerbed[i] == 0 && flowerbed[i-1] == 0&&flowerbed[i+1]==0) {
                    n--;
                    flowerbed[i] = 1;
                    i++;
                    //该位置能种花,则下一位置肯定不能种花,所以i直接+1,跳到i+2位置继续判断
            }

        }
            
        return n <= 0;
    
    }

语法收获:(1)vector首尾插入元素方法:v.push_back(元素值),v.insert(v.begin()+i,元素值),(insert可以用于插入任意指定位置)。

1.5 用最少数量的箭引爆气球(452)

思路:这题与不重叠子区间类似,只要两个气球直接范围区间存在重叠,就可以用同一支箭引爆它们。如下图所示

fig1

因此,我的思路是先对区间排序,从头开始遍历,若存在公共子区间则可以共用一支箭,同时更新公共子区间;否则需要用另外一支箭。

int findMinArrowShots(vector<vector<int>>& points) {
         sort(points.begin(), points.end(), [](vector<int> x, vector<int> y) {
            if (x[0] != y[0])return x[0] < y[0];
            else return x[1] < y[1];
            });

        int Pstart = points[0][0], Pend = points[0][1];//存储重叠区间
        int sum = 1, i,size=points.size();
        for (i = 1; i < size; i++) {
            if (points[i ][0] <= Pend) {
                //说明两个气球在横轴上有重叠,可以用同一根箭
                Pstart = points[i][0];
                if (points[i][1] < Pend) Pend = points[i][1];
                 
            }
            else {//不重叠,要用另一只箭
                sum++;
                Pstart = points[i][0];
                Pend = points[i][1];
            }        
        }
        return sum;

实际上,可以进一步简化,我们不需要保存整个公共子区间,只需保存其右边界即可(即箭可以以发射的最右坐标)。因为我们可以发现,不管新气球是否需要另一支箭,这个右标都是要变得,而左标并不用于比较,是无用的。

故修改为如下,但这时注意要改为按右区间排序,否则会出错,因为按左区间排序,可能导致最右下标修改后变小。

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if (points.empty()) {
            return 0;
        }
        sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>& v) {
            return u[1] < v[1];
        });
        int pos = points[0][1];
        int ans = 1;
        for (const vector<int>& balloon: points) {
            if (balloon[0] > pos) {
                pos = balloon[1];
                ++ans;
            }
        }
        return ans;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值