算法之贪心算法

文章介绍了贪心算法的概念,强调其逐步获取最优解的特性,并指出找到全局最优解需具备最优子结构和最优贪心选择属性。通过三个LeetCode问题(盛最多水的容器、加油站、摆动序列)展示了贪心算法的应用,同时提醒在实际应用中需谨慎判断是否适合使用贪心算法。
摘要由CSDN通过智能技术生成

一)概念
贪心算法(Greedy Alogorithm)又叫登山算法,它的根本思想是逐步到达山顶,即逐步获得最优解,是解决最优化问题时的一种简单但是适用范围有限的策略。

贪心算法没有固定的框架,算法设计的关键是贪婪策略的选择。贪心策略要无后向性,也就是说某状态以后的过程不会影响以前的状态,至于当前状态有关。

贪心算法是对某些求解最优解问题的最简单、最迅速的技术。某些问题的最优解可以通过一系列的最优的选择即贪心选择来达到。但局部最优并不总能获得整体最优解,但通常能获得近似最优解。

在每一步贪心选择中,只考虑当前对自己最有利的选择,而不去考虑在后面看来这种选择是否合理。

二)找出全局最优解的要求
在遇见问题时如何确定是否可以使用贪心算法解决问题,那么决定一个贪心算法是否能找到全局最优解的条件是什么呢?其实就是以下两点:

最优子结构(optimal subproblem structure,和动态规划中的是一个概念)
最优贪心选择属性(optimal greedy choice property)
三)求解时应考虑的问题
1.候选集合S
为了构造问题的解决方案,有一个候选集合C作为问题的可能解,问题的最终解均取自于候选集合C。
2.解集合S
随着贪心选择的进行,解集合不断扩展,直到构成一个满足问题的完整解。
3.解决函数solution
检查解集合是否构成问题的完整解。
4.选择函数select
即贪心策略,这是贪心算法的关键,它指出哪个候选对象有希望构成成问题的解。
5.可行函数feasible
检查解集合中加入一个候选对象是否可行,即解集合扩展后是否满足约束条件。

四)基本步骤
贪心算法使用基本步骤:
1.从问题的某个初始解出发
2.采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个不分解,缩小问题的范围或规模。
3.将所有的部分解综合起来,得到问题的最终解。

五)贪心策略选择
贪心算法的原理是通过局部最优来达到全局最优,采用的是逐步构造最优解的方法。在每个阶段,都做出一个看上去最优的,决策一旦做出,就不再更改。

要选出最优解可不是一件容易的事,要证明局部最优为全局最优,要进行数学证明,否则就不能说明为全局最优。

很多问题表面上看来用贪心算法可以找到最优解,实际上却把最优解给漏掉了。这就像现实生活中的“贪小便宜吃大亏”。所以我们在解决问题的时候,一定要谨慎使用贪心算法,一定要注意这个问题适不适合采用贪心算法。
————————————————
版权声明:本文为CSDN博主「Kk.巴扎嘿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_49370884/article/details/126247776

11. 盛最多水的容器 - 力扣(LeetCode)

用一个左右指针,不断计算容积,记录最大值,左右指针哪面小,哪面就进行移动。

public int maxArea(int[] height) {
        //设置左右指针
        int left = 0;
        int right = height.length - 1;
        int max = 0;
        while (left <= right){
            //计算容积,用max记录最大值
            max = Math.max(max,(right - left) * Math.min(height[right],height[left]));
            //哪边低哪边就动
            if (height[left] <= height[right]){
                left ++;
            }else {
                right --;
            }
        }
        return max;
    }

134. 加油站 - 力扣(LeetCode)

用labuladong的算法

如果i到j点无法到达,那么i到j点之间的点一定不能作为起点。

public static int canCompleteCircuit(int[] gas, int[] cost) {
        //记录全局油量剩余
        int sum = 0;
        for (int i = 0; i < gas.length; i++) {
            sum += gas[i] - cost[i];
        }
        if (sum < 0){
            //全局不可能有解
            return  -1;
        }
        // 记录油箱中的油量
        int tank = 0;
        // 记录起点
        int start = 0;
        for (int i = 0; i < gas.length; i++) {
            tank += gas[i] - cost[i];
            if (tank < 0) {
                // 无法从 start 到达 i + 1
                // 所以站点 i + 1 应该是起点
                tank = 0;
                start = i + 1;
            }
        }
        return start == gas.length ? 0 : start;
    }

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

这里贪心理解不够到位,用一下别人的算法

if len_nums == 1:
        return 1

    direction = None

    res = 0 # 统计变化次数
    
    for i in range(1, len_nums):
        # 统计变化次数

        # 无变化
        if nums[i] == nums[i-1]:
            continue
        
        # 有变化:升高了
        elif nums[i] - nums[i-1] > 0:
            # 如果上一次也是升高,不要算进去,因为其实不是摆动
            if direction == 1:
                continue
            
            direction = 1
            res += 1
        
        # 有变化:降低了
        else:
            # 如果上一次也是降低,不要算进去,因为其实不是摆动
            if direction == 0:
                continue
            
            direction = 0
            res += 1
    

    return res+1 # 因为统计的是变化的次数,最终的结果是序列的长度,所以需要+1.
public static int wiggleMaxLength(int[] nums) {
        if (nums.length == 1)
            return 1;
        int flag;
        int cnt;
        if (nums[1] - nums[0] > 0){
            cnt = 2;
            flag = 1;
        } else if (nums[1] == nums[0]) {
            cnt = 1;
            flag = 0;
        }else {
            cnt = 2;
            flag = -1;
        }

        for (int i = 1; i < nums.length - 1; i++) {
            if (nums[i] == nums[i + 1]){
                //相等就直接跳过
                continue;
            }else if (nums[i] < nums[i + 1]){
                //开始增
                if (flag == 1){
                    //前一个是增,那么就不加入
                    continue;
                }else {
                    cnt++;
                    flag = 1;
                }
            }else {
                //开始减
                if (flag == -1){
                    //前一个是减,不加入
                    continue;
                }else {
                    cnt++;
                    flag = -1;
                }
            }
        }
        return cnt;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值