贪心算法算法,完全零基础小白教程,不是计算机的都能学会!超详解

目录

一、基本概念

二、举几个例子,便于理解

1、找零问题

2、最小路径和

3、背包问题

1)只考虑体积的贪心策略:

2) 只考虑价值的贪心策略:

三、贪心策略的特点

四、贪心策略证明

四、如何学习贪心

五、例题分析

第一题

1、题目描述

2、贪心策略分析

3、代码

 第二题

1、题目描述

2、贪心策略分析

3、代码

 第三题

1、题目描述

2、贪心策略分析

3、代码

 第四题

1、题目描述

2、贪心策略分析

3、代码


一、基本概念

什么是贪心算法?
贪心算法不是一个具体的算法,而是一个策略。
具体的策略是:
1、把解决问题的过程氛围若干步
2、解决每一步,都选择当前看起来“最优”的解法
3、最后希望得到一个全局最优解。

二、举几个例子,便于理解

1、找零问题

你是一个超市前台,你有面额为【20,10,5,1】的零钱,数量不限
有个顾客来买东西,要找零50块。
你要给他找零,要求是给的钞票数最少。
按照贪心的策略,核心是我只考虑当前步数的最优的选法。
所以,按照贪心策略,我不管三七二十一,我不管后面怎么样子,我就只管当前的情况。
所以,50块,我先找两张最大的20
剩下10块,然后找一张10块。
结束。

2、最小路径和

以下有一个表格,

每一次只能向下或者向右走,现在要求从左上角走到右下角走的路径最小。
贪心法的策略就是:我不管三七二十一,那个小,我就走那个。我不管后面怎么样子,我就只管现在这一步。
因此:

3、背包问题


假设我们有以下几种物品,每种物品有价值和体积两个属性:
物品 A:价值 10,体积 2
物品 B:价值 8,体积 3
物品 C:价值 5,体积 4
物品 D:价值 3,体积 1
背包体积为8。现在要求尽可能的装东西,体积不超过8,要求价值最大化。

1)只考虑体积的贪心策略:


贪心的策略就是:我不管你别的,那个小,我选那个。
所以我直接选了8个D,3*8 -21;
但是实际上最佳的选择是4个A,价值为40.

2) 只考虑价值的贪心策略:


别的我不管,那个价值大我放那个。
所以按照价值从大到小的顺序选择物品放入背包。
选择4个价值最大的A,总价值为40.

三、贪心策略的特点

根据上述三个简单例子的理解,我们可以总结归纳贪心策略特点如下:
1、贪心策略没有标准和模板可以套用,需要具体问题具体分析
2、贪心策略所得结果无法保证最优解(因此,需要严格证明策略的正确性)

四、贪心策略证明

常用证明方法(数学方法):
1、贪心选择性质:证明贪心算法每一步的选择都是局部最优的,即当前情况下的最佳选择,不考虑未来可能发生的情况。
2、反证法:假设贪心算法得到的解不是最优解,然后推导出这个假设的结果与实际情况不符,从而证明贪心算法得到的解必须是最优的。
3、交换证明:通过比较贪心算法选择的解和任何其他算法选择的解,证明贪心算法的解比其他算法的解更优或者至少一样好。
4、拼接法:通过将贪心算法得到的局部最优解与其他算法得到的解拼接或组合,证明贪心算法整体上是最优的。

四、如何学习贪心?

根据以上内容,我们怎么学习贪心?
1、遇到不会做的贪心题目,非常正常,非常常见。
不会就去查,就去搜,就去模仿。
先积累模仿,再总结经验,慢慢积累,找感觉。
这就够了,不要想太多。
2、其次,慢慢的,要学会自己去证明贪心策略正确性的过程。

五、例题分析

第一题

1、题目描述

860. 柠檬水找零

  在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

2、贪心策略分析

5元不用找,10元找5元,20元找一张10和一张5,而不是3张5。每一次找零都尽可能的留下5,把10兑换出去。

3、代码

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int five = 0, ten = 0;
        for(auto e : bills)
        {
            if(e == 5)
            {
                five++;
            }
            else if(e == 10)
            {
                if(five == 0)
                {
                    return false;
                }
                else
                {
                    five--;
                    ten++;
                }
            }
            else //20
            {
                if(ten >= 1 && five >= 1)
                {
                    ten--;
                    five--;
                }
                else if(five >= 3)
                {
                    five -= 3;
                }
                else 
                {
                    return false;
                }
            }
            
        }
        return true;
    
    }
};

 第二题

1、题目描述

2208. 将数组和减半的最少操作次数 - 力扣(LeetCode)

给你一个正整数数组 nums 。每一次操作中,你可以从 nums 中选择 任意 一个数并将它减小到 恰好 一半。(注意,在后续操作中你可以对减半过的数继续执行操作)

请你返回将 nums 数组和 至少 减少一半的 最少 操作数。

2、贪心策略分析

每一次选择该数组中最大数字减半,可以使用一个堆,c++中对应的容器是优先队列,priority_queue。

3、代码

class Solution {
public:
    int halveArray(vector<int>& nums) {
        //用优先队列
        //每一次选取top出队列,减半,再插入队列
        //如此保证每一次选择得数据都是最大的
        std::priority_queue<double> q;
        double sum = 0;
        for(auto e : nums)
        {
            sum += e;
            q.push(e);
        }
        double count = 0;
        double half = sum / 2;
        double ret = sum ;

        while(!q.empty())
        {
            double top = q.top() / 2;
            q.pop();
            q.push(top);
            ret -= top;
            count++;
            
            if(ret <= half)
            {
                break;
            }
            
        }
        return count;
    }
};

 第三题

1、题目描述

179. 最大数 - 力扣(LeetCode)

2、贪心策略分析

本题的本质是确定元素的先后顺序
但是,排序规则不同于以往的大小,而是以结果大小为标准。
因此,贪心策略:每一次选择,选择两个数字进行组合,看a和b组合,是ab大还是ba大。

lambda表达式:
[](参数列表){返回值}

3、代码

class Solution {
public:
    string largestNumber(vector<int>& nums) {
        //转换成字符串
        vector<string> v;
        for(auto x : nums)
        {
            v.push_back(to_string(x));
        }
        
            //按照规则排序
            //在范围内进行排序,同时使用lambad表达式
            sort(v.begin(),v.end(),[](const string& s1, const string& s2)
            {
                return s1 + s2 > s2 + s1;
            });
        

        string ret;
        for(auto& e : v)
        {
            ret += e;
        }

        if(ret[0] == '0')
        {
            return "0";
        }

        return ret;
    }
};

 第四题

1、题目描述

376. 摆动序列 - 力扣(LeetCode)

2、贪心策略分析

贪心策略:
当升序时,选择升序序列最大的,保证后面有更多的降序数字满足摆动
当降序时,选择降序序列最小的,保证后面有更多的升序数字满足摆动
即,波峰和波谷。

怎么判断该数字是不是波峰/波谷?
设置状态
设置一个数字的左边和右边
如果left*right<0
说明,要么是波峰,要么是波谷。

3、代码

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.size() < 2)
            return 1;

        int ret = 0;
        int left = 0;

        for(int i = 0; i < nums.size() - 1; i++)
        {
            int right = nums[i+1] - nums[i];
            if(right == 0)//非波峰/谷
                continue;

            if(left * right <= 0)//该点是一个波峰/波谷
                ret++;

            left = right;
        }

            return ret + 1;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二十5画生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值