[日记]LeetCode算法·十四——贪心②

文章介绍了如何使用贪心算法解决LeetCode中的几个问题,包括加油站问题、分发糖果、柠檬水找零、根据身高重建队列和最少数量的箭引爆气球等,强调了从全局和局部最优角度出发解决问题的重要性,并提供了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 加油站

LeetCode:加油站
值得注意的是,由于每一次加油量和耗油量是固定的,这意味着邮箱剩余油量的变化曲线是固定的,只因为起点的不同而存着严格的上下平移关系。
因此从全局最优角度出发,剩余油量最低的站点作为起点,即可保证所有站点的剩余油量>=0(可行的情况下)。
另一种思想,从局部最优角度出发,从0起步,一旦到达站点i剩余油量<0,那么[0,i]的所有站点都不应该被作为起步点,因为这样的话到这里就一定没油了,更替为i+1作为起步点(这样可以保证区间的左端点剩余油量必定大于0)

全局最优
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int sum=0;
        int min_val=gas[0]-cost[0];
        int index=0;
        for(int i=0;i<gas.size();i++)
        {
            //sum为从0出发,到i+1处的油箱剩余量
            //无论什么方式出发,每个站的加油量是固定的,因此油箱剩余量变化曲线形状固定
            //剩余量最低的时候,代表着到达i+1处油量最紧张
            //以此作为起点,就相当于剩余量为0,那其他地方的剩余量必然>=0
            sum+=gas[i]-cost[i];
            if(min_val>=sum)
            {
                index=(i+1)%gas.size();
                min_val=sum;
            }
        }
        if(sum<0)return -1;
        return index;
    }
};
局部最优(贪心)
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum=0;
        int totalSum=0;
        int index=0;
        for(int i=0;i<gas.size();i++)
        {
            int rest=gas[i]-cost[i];
            curSum+=rest;
            totalSum+=rest;
            if(curSum<0)
            {
                curSum=0;
                index=i+1;
            }
        }
        if(totalSum<0)return -1;
        return index;
    }
};

2 分发糖果

LeetCode:分发糖果
对于这种复杂的极值点求解,一定要用贪心将算法解耦,将复杂的两端判断转化为两次贪心的一段判断
本题中,先从前到后遍历,将只看左边的最少糖果分发,再从后向前遍历,将只看右边的最少糖果与(左边最小糖果)比较,取最大值分配
取最大值的原因在于,这是从一侧的要求看来必须要满足的最少糖果,而必须同时满足两侧的要求。

class Solution {
public:
    int candy(vector<int>& ratings) {
        vector<int> nums(ratings.size(),1);
        //从1开始,只要比左边大,就多发一颗糖
        for(int i=1;i<ratings.size();++i)
        {
            if(ratings[i]>ratings[i-1])
                nums[i]=1+nums[i-1];
        }
        //从n-2开始,必要比右边大,就必须取已有值(从左边看的结果)和(从右看结果)中选最大值
        for(int i=ratings.size()-2;i>=0;--i)
        {
            if(ratings[i]>ratings[i+1])
            {
                nums[i]=max(nums[i],1+nums[i+1]);
            }
        }
        int count=0;
        for(int x:nums)count+=x;
        return count;
    }
};

3 柠檬水找零

LeetCode:分发饼干

虽然知道是贪心,但感觉更像是常识题,甚至改一改零钱金额还能变成动态规划题。

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int num5=0;
        int num10=0;
        for(int i=0;i<bills.size();i++)
        {
            //5美元就收入
            if(bills[i]==5)
                ++num5;
            //10美元
            else if(bills[i]==10)
            {
                //10美元零钱+1
                ++num10;
                //可以找零,5美元零钱减少
                if(num5>=1)
                    --num5;
                //不能找零,false
                else
                    return false;
            }
            //20美元
            else
            {
                //尽可能地用10美元+5美元找零
                if(num10>=1 && num5>=1)
                {
                    --num10;
                    --num5;
                    continue;
                }
                else if(num5>=3)
                {
                    num5-=3;
                    continue;
                }
                else
                {
                    return false;
                }
            }
        }
        return true;
    }
};

4 根据身高重建队列

LeetCode:分发饼干
又是同时面对h和k的双重规范,应该以其中一者作为标准进行排序,之后再慢慢调整。
相对理想的是使用身高h作为排序标准,从高到低排列,h一致的k小的在前。
1 排序后,高的在前,意味着people=[h,k],前k个必然比h高,直接将people插入k即可。
2 从高到低的插入,因为k必然<=i(代表着有i个人比h高,如果i<k,则k的意义不成立)
3 所以people插入k的位置后,对于i+1的people’而言,前面数组依然都大于people’的h’只是内部发生了变化,依然保证前k’个必然比h’高,直接将people’插入k’即可

1 身高排序
class Solution {
static bool cmp(vector<int> a,vector<int> b)
{
    //高的在前
    if(a[0]>b[0])
        return true;
    //一样高的,k小的在前
    else if(a[0]==b[0])
        return a[1]<=b[1];
    else
        return false;
}
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        vector<vector<int>> queue;
        //排序后,高的在前,意味着people=[h,k],前k个必然比h高,直接将people插入k即可
        //从高到低的插入,因为k必然<=i(代表着有i个人比h高,如果i<k,则k的意义不成立)
        //所以people插入k的位置后,对于i+1的people'而言,前面数组依然都大于people'的h'
        //只是内部发生了变化,依然保证前k'个必然比h'高,直接将people'插入k'即可
        for(int i=0;i<people.size();++i)
        {
            //一般而言,i>=k是显然的
            int position=people[i][1];
            queue.insert(queue.begin()+position,people[i]);
        }

        return queue;
    }
};
2 k排序(接近暴力法)
class Solution {
static bool cmp(vector<int> a,vector<int> b)
{
    if(a[1]<b[1])
        return true;
    else if(a[1]==b[1])
        return a[0]<=b[0];
    else
        return false;
}
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        vector<vector<int>> queue;

        queue.push_back(people[0]);
        int count=1;
        int larger=0;
        while(count<people.size())
        {
            vector<int> elem=people[count++];
            larger=0;
            for(int i=0;i<queue.size();i++)
            {
                if(queue[i][0]>=elem[0])
                {
                    ++larger;
                }
                if(larger>elem[1])
                {
                    queue.insert(queue.begin()+i,elem);
                    break;
                }
                else if(i==queue.size()-1 && larger==elem[1])
                {
                    queue.insert(queue.begin()+i+1,elem);
                    break;
                }
            }
        }
        return queue;
    }
};

5 用最少数量的箭引爆气球

LeetCode:用最少数量的箭引爆气球
对气球起始位置进行排序,并根据终点位置调整发射点,发射点始终为这一轮射击气球的最小终止位置。一旦起始位置超过发射点,就需要发射第二颗子弹

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);
        //每一次新发射的子弹,必然在上一次子弹没法射到的气球的右边界
        //即在保证不漏气球的情况下,每一发子弹都尽可能靠右
        //那么很显然,第一发子弹的初始预定发射点应该是points[0][1]
        //并在过程中对发射点进行调整(向左调整)
        int edge=points[0][1];
        int shots=1;
        for(int i=1; i<points.size(); ++i)
        {
            //这发子弹射不到该气球
            if(points[i][0]>edge)
            {
                //更改发射点
                edge=points[i][1];
                //发射
                ++shots;
            }
            //试试调整
            else
            {
                edge=min(points[i][1],edge);
            }
        }
        return shots;
    }
};

6 总结

贪心算法感觉毫无规律可言,完全就是靠着自己的思路去硬生生地解题,有点痛苦。
只能依靠多做题培养题感,令人忧伤啊。
——2023.2.27

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值