算法训练Day34 贪心算法专题 | LeetCode1005.K次取反后最大化的数组和 ;134.加油站;135.分发糖果(不要两头兼顾,一边一边处理)

news2025/5/17 19:05:21

前言:

算法训练系列是做《代码随想录》一刷,个人的学习笔记和详细的解题思路,总共会有60篇博客来记录,计划用60天的时间刷完。 

内容包括了面试常见的10类题目,分别是:数组,链表,哈希表,字符串,栈与队列,二叉树,回溯算法,贪心算法,动态规划,单调栈。

博客记录结构上分为 思路,代码实现,复杂度分析,思考和收获,四个方面。

如果这个系列的博客可以帮助到读者,就是我最大的开心啦,一起LeetCode一起进步呀;)

目录

LeetCode1005.K次取反后最大化的数组和 

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考与收获

Leetcode134. 加油站

方法一: 暴力解法

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考与收获

方法二:宏观的贪心算法

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考与收获

方法三:贪心解法

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考与收获

总结

Leetcode135. 分发糖果

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考与收获


LeetCode1005.K次取反后最大化的数组和 

链接:1005. K 次取反后最大化的数组和 - 力扣(LeetCode)

1. 思路

本题思路其实比较好想了,如何可以让数组和最大呢?

贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。

局部最优可以推出全局最优。

那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。

那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。

虽然这道题目大家做的时候,可能都不会去想什么贪心算法,一鼓作气,就AC了。

我这里其实是为了给大家展现出来 经常被大家忽略的贪心思路,这么一道简单题,就用了两次贪心!

那么本题的解题步骤为:

  • 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
  • 第二步:从前向后遍历,遇到负数将其变为正数,同时K--
  • 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
  • 第四步:求和

2. 代码实现

# time:O(NlogN);space:O(N)
class Solution(object):
    def largestSumAfterKNegations(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        # 将数组nums按照绝对值大小,从大到小排序
        sortedNums = sorted(nums,key=abs,reverse=True)
        index = 0
        # 从大的开始,把负数变成正数
        while k>0 and index<len(nums):
            if sortedNums[index]<0:
                sortedNums[index] *= -1
                k -= 1
            index += 1
        # 如果所有数都变成非负数之后,K仍为正数
        # 就针对最后一个绝对值最小的数,把K都用完
        if k>0:
            sortedNums[-1] *= (-1)**k
        return sum(sortedNums)

3. 复杂度分析

  • 时间复杂度:O(logN)

    其中N为数组nums的长度,首先需要对数组nums进行排序的时间复杂度为O(NlogN);然后需要遍历一遍数组,从大到小把尽可能多的负数变成正数,O(N),还有sum操作,复杂度O(N),总体时间复杂度O(NlogN);

  • 空间复杂度:O(N)

    其中N为nums的长度,sorted排序新建了一个数组,O(N);

4. 思考与收获

  1. 空间复杂度上还可以继续优化,不用sorted,而用sort,就会在原数组上进行操作,不会新建一个数组,复杂度可以降低为O(1),代码如下:

    # time:O(NlogN);space:O(1)
    class Solution(object):
        def largestSumAfterKNegations(self, nums, k):
            """
            :type nums: List[int]
            :type k: int
            :rtype: int
            """
            # 将数组nums按照绝对值大小,从大到小排序
            nums.sort(key=abs,reverse=True)
            index = 0
            # 从大的开始,把负数变成正数
            while k>0 and index<len(nums):
                if nums[index]<0:
                    nums[index] *= -1
                    k -= 1
                index += 1
            # 如果所有数都变成非负数之后,K仍为正数
            # 就针对最后一个绝对值最小的数,把K都用完
            if k>0:
                nums[-1] *= (-1)**k
            return sum(nums)
    
  2. 贪心的题目如果简单起来,会让人简单到开始怀疑:本来不就应该这么做么?这也算是算法?我认为这不是贪心?本题其实很简单,不会贪心算法的同学都可以做出来,但是我还是全程用贪心的思路来讲解。因为贪心的思考方式一定要有!如果没有贪心的思考方式(局部最优,全局最优),很容易陷入贪心简单题凭感觉做,贪心难题直接不会做,其实这样就锻炼不了贪心的思考方式了。所以明知道是贪心简单题,也要靠贪心的思考方式来解题,这样对培养解题感觉很有帮助!

Reference:代码随想录 (programmercarl.com)

本题学习时间:30分钟。


Leetcode134. 加油站

 链接:134. 加油站 - 力扣(LeetCode)

方法一: 暴力解法

1. 思路

遍历每一个加油站为起点的情况,模拟一圈;

如果跑了一圈,中途没有断油,而且最后油量大于等于0,说明这个起点是ok的

2. 代码实现

# 解法一: 暴力解法
# Python 会超时
# time:O(N^2);space:O(1)
class Solution(object):
    def canCompleteCircuit(self, gas, cost):
        """
        :type gas: List[int]
        :type cost: List[int]
        :rtype: int
        """
        # 每个起点都尝试一遍
        for i in range(len(cost)):
            # 先走到i的下一步
            # 记录剩余的油量
            rest = gas[i] - cost[i]
            index = (i+1)%len(cost)
            # 模拟以i为起点跑下剩余的一圈
            while rest>0 and index!=i:
                rest += gas[index]-cost[index]
                index = (index+1)%len(cost)
            # 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
            if rest>=0 and index==i: return i 
        return -1

3. 复杂度分析

  • 时间复杂度:O(N^2)

    其中N为加油站的个数,也是gas数组和cost数组的长度,需要以每个加油站为起点,模拟跑圈跑一遍,所以时间复杂度为O(N^2);

  • 空间复杂度:O(1)

    只有常数个变量来记录;

4. 思考与收获

  1. for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历,要善于使用while;
  2. 暴力的方法思路比较简单,但代码写起来也不是很容易,关键是要模拟跑一圈的过程。

方法二:宏观的贪心算法

1. 思路

直接从全局进行贪心选择,情况如下:

  • 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
  • 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
  • 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点。

2. 代码实现

# 解法二:宏观贪心
# time:O(N);space:O(1)
class Solution(object):
    def canCompleteCircuit(self, gas, cost):
        """
        :type gas: List[int]
        :type cost: List[int]
        :rtype: int
        """
        totalSum = 0
        totalMin = float("inf")
        n = len(gas)
        for i in range(n):
            totalSum += gas[i]-cost[i]
            totalMin = min(totalMin,totalSum)
        if totalSum<0: return -1
        if totalMin>=0: return 0
        for j in range(n-1,-1,-1):
            totalMin += gas[j]-cost[j]
            if totalMin >=0: return j

3. 复杂度分析

  • 时间复杂度:O(N)

    从头到尾遍历数组不超过两遍,所以O(N);

  • 空间复杂度:O(1);

    只有常数个变量需要保存;

4. 思考与收获

  1. **其实Carl不认为这种方式是贪心算法,因为没有找出局部最优,而是直接从全局最优的角度上思考问题。**但这种解法又说不出是什么方法,这就是一个从全局角度选取最优解的模拟操作,但不管怎么说,解法毕竟还是巧妙的,不用过于执着于其名字称呼。

方法三:贪心解法

1. 思路

可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

每个加油站的剩余量rest[i]为gas[i] - cost[i]。

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,起始位置从i+1算起,再从0计算curSum。

如图:

那么为什么一旦[i,j] 区间和为负数,起始位置就可以是j+1呢,j+1后面就不会出现更大的负数?

如果出现更大的负数,就是更新j,那么起始位置又变成新的j+1了。

而且j之前出现了多少负数,j后面就会出现多少正数,因为耗油总和是大于零的(前提我们已经确定了一定可以跑完全程)。

那么局部最优:当前累加rest[j]的和curSum一旦小于0,起始位置至少要是j+1,因为从j开始一定不行。全局最优:找到可以跑一圈的起始位置

局部最优可以推出全局最优,找不出反例,试试贪心!

2. 代码实现

# 方法三: 贪心算法
# time:O(N);space:(1)
class Solution(object):
    def canCompleteCircuit(self, gas, cost):
        """
        :type gas: List[int]
        :type cost: List[int]
        :rtype: int
        """
        start = 0
        curSum = 0
        totalSum = 0
        n = len(gas)
        for i in range(n):
            curSum += gas[i] - cost[i]
            totalSum += gas[i] -cost[i]
            # 当前累加rest[i]和 curSum一旦小于0
            if curSum<0:
                # 起始位置更新为i+1,curSum从0开始
                curSum = 0
                start = i+1
        # 说明怎么走都不可能跑一圈了
        if totalSum<0: return -1
        return start

3. 复杂度分析

  • 时间复杂度:O(N)

    从头到尾遍历数组,所以O(N);

  • 空间复杂度:O(1);

    只有常数个变量需要保存;

4. 思考与收获

  1. 说这种解法为贪心算法,才是是有理有据的,因为全局最优解是根据局部最优推导出来的;

总结

  1. 对于本题首先给出了暴力解法,暴力解法模拟跑一圈的过程其实比较考验代码技巧的,要对while使用的很熟练;
  2. 然后给出了两种贪心算法,对于第一种贪心方法,其实我认为就是一种直接从全局选取最优的模拟操作,思路还是好巧妙的,值得学习一下;
  3. 对于第二种贪心方法,才真正体现出贪心的精髓,用局部最优可以推出全局最优,进而求得起始位置。

Reference: 代码随想录 (programmercarl.com)

本题学习时间:60分钟。


Leetcode135. 分发糖果

链接:135. 分发糖果 - 力扣(LeetCode)

1. 思路

这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼;

💡 先确定右边评分大于左边的情况(也就是从前向后遍历)

  • 此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果
  • 全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果;局部最优可以推出全局最优。

如果ratings[i] > ratings[i - 1] 那么[i]的糖 一定要比[i - 1]的糖多一个,所以贪心:candyVec[i] = candyVec[i - 1] + 1

如图:

💡 再确定左孩子大于右孩子的情况(从后向前遍历)

遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?

因为如果从前向后遍历,根据 ratings[i + 1] 来确定 ratings[i] 对应的糖果,那么每次都不能利用上前一次的比较结果了;

所以确定左孩子大于右孩子的情况一定要从后向前遍历!

如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。

那么又要贪心了,局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量即大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。

局部最优可以推出全局最优。

所以就取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,candyVec[i]只有取最大的才能既保持对左边candyVec[i - 1]的糖果多,也比右边candyVec[i + 1]的糖果多

2. 代码实现

# 贪心算法
# time:O(N);space:O(N)
class Solution(object):
    def candy(self, ratings):
        """
        :type ratings: List[int]
        :rtype: int
        """
        candy = [1]*len(ratings)
        # 从前向后
        for i in range(1,len(ratings)):
            if ratings[i]>ratings[i-1]:
                candy[i] = candy[i-1]+1
        # 从后向前
        for i in range(len(ratings)-2,-1,-1):
            if ratings[i]>ratings[i+1]:
                candy[i] = max(candy[i],candy[i+1]+1)
        return sum(candy)

3. 复杂度分析

  • 时间复杂度:O(N)

    其中N为数组ratings的长度,也为孩子的个数;本解法需要从左到右遍历一遍,再从右向左遍历一遍,还需要sum数组candy,总的时间复杂度O(N);

  • 空间复杂度:O(N)

    其中N为数组ratings的长度,也为孩子的个数;需要新建一个数组candy来记录每个孩子的糖果数;

4. 思考与收获

  1. 这在leetcode上是一道困难的题目,其难点就在于贪心的策略,如果在考虑局部的时候想两边兼顾,就会顾此失彼;

  2. 那么本题我采用了两次贪心的策略:

    • 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
    • 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。

    这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。

Reference: 代码随想录 (programmercarl.com)

本题学习时间:60分钟。


本篇学习时间近3小时,总结字数6000+;本篇学习了三道贪心算法的题目,第一题相对简单,甚至写完了都不知道自己用了贪心算法的思路,要刻意训练自己这种意识,第二题的贪心思路不太好想,重点是方法二,第三题是不能同时两头兼顾,必须一边一边处理。(求推荐!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/47835.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于人工势场法的移动机器人路径规划研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 路径规划是移动机器人领域的热点研究方向&#xff0c;人工势场法已在工业机器人路径规划中得到广泛应用&#xff0c;近年来正逐…

RKMEDIA--VI的使用

在上一篇文章RKMEDIA使用简介中简单的介绍了rkmedia的组成部分&#xff0c;本章主要聊聊VI模块。 rkmedia中的VI模块主要可以从两个方式获取流&#xff1a;直接打开video节点的方式、使用rk平台的rkaiq。 1、直接打开video节点的方式 顾名思义只需要在vi初始化中配置VI_CHN_AT…

Redeis缓存查询基于元注解与AOP结合使用——不过时的优雅

Redeis缓存查询基于元注解与AOP结合使用 根据优化需要&#xff0c;数据查询的时候无法避免的使用Redis基于缓存查询&#xff0c;进而减少对于数据库的查询压力&#xff0c;对于过多的方法基于缓存存储&#xff0c;为提高代码的复用性&#xff0c;采用一种不过时的写法。 整体的…

spring JPA整合hibernate,IDEA社区版,Java

spring JPA整合hibernate&#xff0c;IDEA社区版&#xff0c;Java 本文基于IDEA社区版&#xff0c;不是IDEA企业版。 &#xff08;1&#xff09;首先用IDEA新建一个spring web项目。参考文章&#xff1a; IDEA社区版(Community Edition)创建Springboot-Web项目&#xff0c;J…

第十二章 使用 Monorepo 方式管理组件生态

组件库一般都会配有周边产品&#xff0c;比如 Admin 、Template、CLI 工具等等。周边产品相当于有关联的多个项目&#xff0c;更准确的说法是多个软件包。这个时候就应该使用 Monorepo 方式组织代码&#xff0c;方便频繁在多个项目间同时交替开发&#xff0c;同时发布&#xff…

图解LeetCode——895. 最大频率栈(难度:困难)

一、题目 设计一个类似堆栈的数据结构&#xff0c;将元素推入堆栈&#xff0c;并从堆栈中弹出 出现频率 最高的元素。 实现 FreqStack 类: FreqStack() 构造一个空的堆栈。void push(int val) 将一个整数 val 压入栈顶。int pop() 删除并返回堆栈中出现频率最高的元素。如果出…

圣杯与双飞翼布局,clip-path,列表与生成元素,计数器

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录圣杯与双飞翼布局clip属性clip-path属性例子&#xff08;不同区域使用不同颜色的导航&#xff09…

【MySQL 18】Docker 安装 MySQL8 .0.30

1、查看可用的 MySQL 版本 访问 MySQL 镜像库地址&#xff1a; https://hub.docker.com/_/mysql?tabtags 。2、拉取 MySQL 8.0.30 镜像 拉取官方的指定版本的镜像&#xff1a; docker pull mysql:8.0.30[rootlocalhost deploy]# docker pull mysql:8.0.30 8.0.30: Pulling…

Gly-Gly-Arg, 54944-27-3/55033-48-2

贻贝信息素的模拟物&#xff0c;诱导各种贻贝物种的聚集&#xff0c;如绒螯虾。GGR还能刺激浮游生物幼虫定居。 编号: 401458中文名称: Gly-Gly-Arg英文名: Gly-Gly-ArgCAS号: 54944-27-3/55033-48-2单字母: H2N-GGR-OH三字母: H2N-Gly-Gly-Arg-COOH氨基酸个数: 3分子式: C10H2…

【C语言初阶(NEW)】五、操作符详解(二)|隐式类型转换|算术转换|操作符的属性

目录 一、表达式求值 1.1 隐式类型转换 1.1.1 什么是整型提升&#xff08;整型提升&#xff09; 1.1.2 整型提升的意义 1.1.3 有符号&#xff08;signed&#xff09;与无符号&#xff08;unsigned&#xff09;的区别 1.1.4 有符号&#xff08;signed&#xff09;类型的整…

Redis学习笔记②实战篇_黑马点评项目

若文章内容或图片失效&#xff0c;请留言反馈。部分素材来自网络&#xff0c;若不小心影响到您的利益&#xff0c;请联系博主删除。 资料链接&#xff1a;https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA&#xff08;提取码&#xff1a;eh11&#xff09;在线视频&#xff1a;…

知道创宇ScanA免费试用|守护网络空间内容安全生命线

《淮南子说山训》中有言&#xff0c;“众曲不容直&#xff0c;众枉不容正&#xff0c;故……众议成林&#xff0c;无翼而飞&#xff0c;三人成市虎”。随着互联网社交、即时通讯工具等社交媒体的蓬勃发展&#xff0c;大众发布、传播和获取信息的方式更加简便、渠道更为广泛。也…

安科瑞 ARCM300-Z-4G 导轨式智慧用电监控装置 猪舍无线火灾探测器

安科瑞 王晶淼/刘芳 1 概述 智慧用电在线监控装置是针对 0.4kV 以下的 TT、TN 系统设计的智能电力装置&#xff0c;具有单、三相交流电测量、四象限电能计量、谐波分析、遥信输入、遥信输出功能&#xff0c;以及 RS485 通讯或 GPRS 无线通讯功能&#xff0c;通过对配电回路的剩…

YOLO V5 详解

YOLO V5 Backbone SPPF SPP 是使用了3个kernel size不一样大的pooling 并行运算。SPPF是将kernel size为5的 pooling 串行运算&#xff0c;这样的运算的效果和SPP相同&#xff0c;但是运算速度加快。因为SPPF减少了重复的运算&#xff0c;每一次的pooling 运算都是在上一次运…

IP-guard产品相关端口和服务名称

数据库 SQL Server (SQLEXPRESS) 服务器 OCULAR V3 SERVER 中继器 OCULAR V3 MIDTIER SERVER 客户端 WINDOWS HELPER SERVICE 报表 OCULAR V3 REPORT SERVER web服务器 Ocular web server,OCULAR Console Web Service 云备份服务器 OCULAR File Cloud Backup Server,OCULAR Fil…

Java——迷你图书管理器(JDBC+MySQL+Apache DBUtils)

​ ✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;乐趣国学的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例…

沉睡者IT - Web3的未来在哪里?

欢迎关注沉睡者IT&#xff0c;点上面关注我 ↑ ↑ 专家说&#xff0c;web3将颠覆现在的互联网 今天我们来讨论一下&#xff0c;web3会颠覆现在的互联网呢&#xff1f; 看了小编往期的作品你应该知道&#xff0c;如果同样的作品发在web3平台上&#xff0c;你将获取到收益。 那…

【笔试强训】Day 5

&#x1f308;欢迎来到笔试强训专栏 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句鸡汤&#x…

强化学习泛化性 综述论文阅读 A SURVEY OF GENERALISATION IN DEEP REINFORCEMENT LEARNING

强化学习泛化性 综述论文阅读摘要一、介绍二、相关工作&#xff1a;强化学习子领域的survey三、强化学习中的泛化的形式3.1 监督学习中泛化性3.2 强化学习泛化性背景3.3 上下文马尔可夫决策过程3.4 训练和测试上下文3.6 应用实例3.7 更可行泛化的其他假设3.8 备注和讨论4. 强化…

SSM整合

整合的思路是&#xff1a; 先创建spring框架 通过spring整合spring mvc 通过spring整合mybatis 工程创建 创建Maven工程–>create for archtype–>webapp 创建项目结构 在recourses目录下创建 dbconfig.properties、log4j.properties、mysqlConfig.xml、springmvc.xml、…