贪心算法详解

2023-05-16

前言

贪心算法是动态规划的一种特殊情况,需要满足更为苛刻的条件:贪心选择。

算法种类时间复杂度
暴力算法指数级别
动态规划多项式级别
贪心算法线性级别

什么是贪心选择性质呢,简单说就是:每一步都做出一个局部最优的选择,最终的结果就是全局最优。注意哦,这是一种特殊性质,其实只有一部分问题拥有这个性质。动态规划可以认为是含有重叠子问题的暴力算法,本质还是要穷举所有解空间,而贪心的精髓在于不用穷举所有解空间,就能找到答案。所以准确说应该是,能够用贪心算法解决的问题,一定能用暴力穷举的方式求解。当然,几乎所有计算机算法问题,都能用暴力穷举的方式求解。
上面虽然说明,一定情况下,贪心算法能够比动态规划达到更好的时间复杂度,但是并不意味着贪心算法比动态规划算法简单,举两个分别应用两个算法的例子:
1、给你一叠纸币,你可以取10张,问怎样能使得取出的钱最多?
2、给你一个箱子体积为N,你有一堆货物,如何尽可能把箱子装满?
上面两个问题分别对应贪心算法与动态规划,第2题使用贪心算法是无法解决的,因为有箱子体积的限制。
下面选则几道比较难的题目体会贪心与动态规划解题的解题。

1、摆动序列

摆动序列
首先看到题目是最值问题,最终的要求是求最长子序列的长度,所以第一个念头是想到动态规划的,恰好我们有一道现成的题目,最长递增子序列,是不是可以借鉴呢?反正我想不到,哈哈哈

class Solution {
public int wiggleMaxLength(int[] nums) {
  if(nums.length==1)
  {
      return nums.length;
  }
  int n=nums.length;
  int prediff=0;
  int curdiff=0;
  int result=1;
  for(int i=0;i<n-1;i++)
  {
      curdiff=nums[i+1]-nums[i];
      if((curdiff>0 && prediff<=0)|| (curdiff<0 && prediff>=0))
      {
          result++;
          prediff=curdiff;   
      }
  }
  return result;
}
}

关于代码中,我在编写的时候出过这样的问题
问题1:
为什么prediff=curdiff; 写在if判断条件之外会导致答案错误?
举一个反例,2,6,6,5,5,4,因为写在里面规避了除去端点之外的prediff=0情况的出现,换句话说,我们并不想判断prediff=0的情况,只是由于要判断起始点与终止点的情况。
还有一个特殊的反例[3,3,3,2,5],这个反例可以很好的证明为什么prediff=0一定要写在循环里面,而不能在开始循环之前进行或者在循环结束之后按特殊条件处理到prediff=0的情况,因为我们不知道真正进入比较的点在那个地方,在这个反例中是在index=2的位置。
问题2:
为什么result初始化为1?
这个可以分几种情况来讨论:
第一:如果只有1个数,按照题目的意思,直接返回1是正确的。
第二:如果有多个数 ,这个下面也可以分为几种情况
1:前两个数一样的情况,进入循环后,没有触发正确的判断机制,没有进入判断,故这时候到下一次循环,result依然应该为1
2:前两个数不一样,正常进入触发,两个数进行一次判断,但是有两个值,所以判断正确进入循环后,result++应该为2

这道题当然还有被称作动态规划的方法去做,其实不应该在贪心算法的范畴里面,但是这道题对于下面的分发糖果(两次贪心起到了很好的启发作用)。
我们可以设置一个上身子序列与下降子序列

class Solution {
public int wiggleMaxLength(int[] nums) {
int up=1;
int down=1;
int n=nums.length;
for(int i=1;i<n;i++)
{
	if(nums[i-1]<nums[i])
	{
		up=down+1;
	}
	else if(nums[i-1]>nums[i])
	{
		down=up+1;
	}
}
return Math.max(down,up);
}
}

2、跳跃游戏

跳跃游戏

这道题相对来说是有难度的,很难想到这样去做,因为这道题要涉及到一个覆盖范围的问题,我们要维护一个叫覆盖范围的东西,用一个变量去表示它,最终其能不能达到最后一个下标范围。

这道题的重点在于如何把下标与value值,结合起来,可以有多种结合的方式,本题选择了相加的方式。

k=Math.max(k,i+nums[i]);
class Solution {
    public boolean canJump(int[] nums) {
        //覆盖范围
        int k=0;
        int n=nums.length;
        for(int i=0;i<n;i++)
        {
            if(i>k)
            {
                return false;
            }
            k=Math.max(k,i+nums[i]);
        }
        return true;
    }
}

3、跳跃游戏II

跳跃游戏2
这道题的思路其实可以不完善的想到一个,但是不敢用,就是让当前的可跳跃步数直接走到最大,但是我们怕漏了中间的大的跳跃数
比如说:3,0,4,1,1,1,3,这时候如果在i=0的时候直接走3步达到i=2时的nums[i]=1,错过了大跳跃数4,这个顾虑和上面一题的顾虑时一样的,还是需要依次遍历,获取覆盖最大值,重点在于不能更改遍历值,不能让自带逻辑修改i。

class Solution {
    public int jump(int[] nums) {
        int curdist=0;//当前跳跃值所能达到的最远下标
        int nextdist=0;//下一次覆盖范围所能达到的最远下标
        int ans=0;
     for(int i=0;i<nums.length;i++)
     {
      nextdist=Math.max(nextdist,i+nums[i]);
      if(i==curdist)//到达了当前所能到达的最远下标,依然在循环内
      {
          if(curdist!=nums.length-1)
          {
              curdist=nextdist;
          		ans++;
              if(curdist>=nums.length-1)
              {
                  break;
              }
          }
          else
          {
              break;
          }
      }
  } 
  return ans;
}
}

注意,由于我们初始条件下设置的curdist与nextdist都等于0,故我们要让循环运行起来,必须要让第一波i=curdist=0进入循环中,故题目中有三个个比较重要的位置,其顺序很重要

    nextdist=Math.max(nextdist,i+nums[i]);
    ...
	ans++;
	if(curdist>=nums.length-1)
		{
		   break;
		}

这道题的第二种方式,将重复的逻辑合并到一起,当循环下标到达curdist之后,就直接步数+1

class Solution {
    public int jump(int[] nums) {
        int curdist=0;//当前跳跃值所能达到的最远下标
        int nextdist=0;//下一次覆盖范围所能达到的最远下标
        int ans=0;
     for(int i=0;i<nums.length-1;i++)
     {
      nextdist=Math.max(nextdist,i+nums[i]);
      if(i==curdist)//到达了当前所能到达的最远下标,依然在循环内
      {	
      	ans++;
      	curdist=nextdist;
      }
  	} 
  return ans;
}
}

4、分发糖果

图

class Solution {
    public int candy(int[] ratings) {
        //分为两次贪心,如果想在一次贪心中解决左右两边的问题,会做不出来
        int n=ratings.length;
        int [] count=new int[n];
        Arrays.fill(count,1);
        //右边的孩子评分高的比左边的孩子多一个糖果
        for(int i=1;i<n;i++)
        {
            if(ratings[i-1]<ratings[i])
            {
                count[i]=count[i-1]+1;
            }
        }

        //左边的孩子评分高的比右边的孩子多一个糖果
        for(int i=n-2;i>=0;i--)
        {
            if(ratings[i]>ratings[i+1])
            {
                count[i]=Math.max(count[i],count[i+1]+1);
            }
        }
        int ret=0;
        for(int i=0;i<n;i++)
        {
            ret+=count[i];
        }
        return ret;
    }
}

我们关注的重点应该是左边的表现大于右边的表现得情况,这个情况下,不能直接的使用count[i]=count[i+1]+1。
因为啊,在次数未知的递增之后,如果出现一次下降,该小孩的糖果值是直接被设置为1的。举下面一个例子:
ratings评分表数组为:[1,2,3,1]
我们可以直接看出相应的每个孩子的糖果数应该为:[1,2,3,1]
但是如果count[i]=count[i+1]+1,那么孩子的糖果数数组应该为:[1,2,2,1],显然这是错误的。

5、无重叠区间

无重叠区间

无重叠区间是一道非常典型的贪心算法应用实例。这道题首先映入眼帘的就是这是一道最值问题,那么对于最值问题,我们首先想到的是动态规划,那么这道题绝对是可以使用动态规划去做的。如同最长递增子序列这道题一样,这道题单纯的使用动态规划是比较难得,需要使用二分查找类型的动态规划。

那么对于这道题的贪心算法,和前面几道题类似,最短(最长问题的反问题)这一类的题目,一定是有一个前后的层次递进关系的,这一次的最长、最短结果的值(中间值)一定是要传递到下一次判断中去。
举几个例子:本文的第一题:最长摆动序列,这道题层次递进的是上一次的差值curdiff,作为这一次的prediff,这样才能保证前后的关联性,这样才能是全局的最大值,如果没有前后的关联性,怎么能叫全局的最大值呢。
本文的第二题、第三题,跳跃游戏与跳跃游戏的变体,这两道题中都有这样一段代码

k=Math.max(k,i+nums[i]);

这段代码的主要作用就在于将上一次可跳跃的最大长度和这一次可跳跃的长度机型层次递进,取其大者。这道题与上面几乎是同一个思路。

class Solution{
 public int eraseOverlapIntervals(int[][] intervals) {
 	//首先对其进行排序,按照先结束的排在前面的原则进行排序的规则
 	Arrays.sort(intervals,new Comparator<int []>(){
 		public int compare(int []o1,int []o2)
 		{
 		 return o1[1]-o2[1];
 		}
 	});
 	int count=1;
 	int end=intervals[0][1];
 	for(int []e:intervals)
 	{
 		int start=e[0];
 		if(start>=end)
 		{
 			count++;
 			end=e[1];
 		}
 	}
 	return count;
 }
}

6、用最少数量的箭射爆气球

射爆气球
这道题的难点在于其层次递进关系。

class Solution {
 public int findMinArrowShots(int[][] points) {
     //和无重叠区间特别像
     Arrays.sort(points,new Comparator<int[]>(){
         public int compare(int []o1,int []o2)
         {
             return Integer.compare(o1[0], o2[0]);
         }
     });
     int count=1;
     for(int i=1;i<points.length;i++)
     {
         if(points[i][0]>points[i-1][1])
         {
             count++;
         }
         else {
             points[i][1] = Math.min(points[i][1],points[i - 1][1]);
         }
     }
     return count;
 }
}

但是这道题有一个值得注意的地方就是我们再排序的时候,不能用简单的

return o1[0]-o2[0];

由于测试用例出现了[[-2147483646,-2147483645],[2147483646,2147483647]],这样比较大小,一个特别大的正数减去一个特别大的负数出现的值会出现溢出。
这个问题的解决,有两种方法:
第一种就如同解决代码中的用Integer包装类的方法进行比较
第二种:

Arrays.sort(points,new Comparator<int[]>(){
    public int compare(int []o1,int []o2)
    {
       if(o1[0]>o2[0])
       {
       	return 1;
       }
       else if(o1[0]<o2[0])
       {
       	return -1;
       }
       else
       {
       	return 0;
       }
    }
});

7、柠檬水找零

柠檬水找零
这道题从逻辑上来讲不是难题,但是我们有一个容易出错的点在于就算我现在手上的钱大于这个要找零的这个钱,也并不一定能找零成功。举一个例子:
对于bills= [5,5,10,10,20]而言,当4次交易过后,我们手上的钱只剩两张10快的,最后一次我们收入一张20的,需要我们找零,能满足条件的应该是一张10元、一张5元的或者三张5元的,但是手里没有5元的,故失败。
所以从逻辑上来讲不是说现存手上的钱的总和大于当次收入的钱就可以,而是要有相应的金额组成。

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int x=0;
        int y=0;
        int z=0;
        for(int bill:bills)
        {
            if(bill==5)
            {
                x++;
            }
            else if(bill==10)
            {
                y++;
                x--;
            }
            else if(bill==20)
            {
                //贪的是什么,这一块能不能看出来,就是贪10块先找
               if(x>0 && y>0)
                {
                    x--;
                    y--;
                }
                else
                {
                    x-=3;
                }
            }
            if(x<0 || y<0)
            {
                return false;
            }
        }
        return true;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

贪心算法详解 的相关文章

  • flashcache原理

    介绍flashcache的文章很多 xff0c 我就不废话了 使用上 xff0c 有余峰老哥的 文章 xff1b 原理上 xff0c 有ningoo同学的 flashcache系列 但是ningoo同学漏掉了device mapper和fl
  • 无人机算法之PID

    xff08 未完成 xff09 一 PID介绍 xff08 百度百科 xff09 PID 控制器 xff08 比例 积分 微分控制器 xff09 是一个在工业控制应用中常见的反馈回路部件 xff0c 由比例单元 P 积分单元 I 和微分单元
  • java:接口、lambda表达式与内部类

    接口 xff08 interface 接口用来描述类应该做什么 xff0c 而不指定他们具体应该如何做 接口不是类 xff0c 而是对符合这个接口的类的一组需求 接口定义的关键词是interface span class token key
  • 卫星系统算法课程设计 - 第二部分 qt的安装与创建项目

    上一篇文章只讲了基本的东西 xff0c 这一篇要完成qt的安装 xff0c 构建项目 xff0c 并且将上一篇的代码导入进去 某比利比例搜qt安装 xff0c 看到qt5 14 2的下载安装 xff0c 跟着做 1 创建项目 创建新项目 x
  • 无人机-材料准备

    xff08 未完成 xff09 一 使用空心杯电机 xff0c 型号8520 xff0c 1S版本 xff0c 约5G每只 二 空心杯机架 xff0c 型号QX90 xff0c 约8 5g 三 使用55MM桨 四 1S 600MA电池 五
  • CMake中链接库的顺序问题

    原文链接 xff1a https blog csdn net lifemap article details 7586363 cmake中链接库的顺序是a依赖b xff0c 那么b放在a的后面 例如进程test依赖a库 b库 a库又依赖b
  • 鸿蒙wifi Demo运行

    title 鸿蒙Wi Fi Demo运行 date 2021 1 1 22 25 10 categories harmony 本文首发于LHM s notes 欢迎关注我的博客 坑有点多 由于之前没有看过wifi的内核态代码 xff0c 所
  • 将TensorFlow训练好的模型迁移到Android APP上(TensorFlowLite)

    将TensorFlow训练好的模型迁移到Android APP上 xff08 TensorFlowLite xff09 1 写在前面 最近在做一个数字手势识别的APP xff08 关于这个项目 xff0c 我会再写一篇博客仔细介绍 xff0
  • 汉诺塔代码图文详解(递归入门)

    游戏规则 xff1a 已知条件存在A B C三根柱子 xff0c A上套有N片圆盘 如下图 目的将A上的所有圆盘移到C上约束条件每次只能移动一片圆盘 xff0c 且整个过程中只能出现小圆盘在大圆盘之上的情况 首先我们模拟 N 61 2 xf
  • STM32 最小系统电路简析

    文章目录 一 最小系统的组成1 供电电路2 外部晶振3 BOOT选择4 复位电路 二 最小系统实例1 STM32F103C8T6最小系统 三 各部分组成简析1 供电电路设计2 外部晶振原理3 BOOT设计4 复位电路设计 一 最小系统的组成
  • 带参数的宏的问题

    include 34 iostream 34 using namespace std define COMPUTE XX a a a 43 a 2 int main int a 61 2 int test1 61 COMPUTE XX 43
  • python_imbalanced-learn非平衡学习包_02_Over-sampling过采样

    python imbalanced learn非平衡学习包 01 简介 python imbalanced learn非平衡学习包 02 Over sampling过采样 后续章节待定 希望各位认可前面已更 您的认可是我的动力 Over s
  • TX2+JetPack3.2.1+opencv3.3.1+caffe+realsense2.0环境配置教程

    TX2 开箱 一共6样 xff0c 开机之后自带ubuntu16 04LTS的系统 xff0c ARMv8的处理器 xff0c 所以有些指令 xff0c 安装包必须与arm结构保持一致 开机之后 xff0c 按照指示进入图形界面 xff1a
  • 初视openwrt

    openwrt是一个微型的嵌入式操作系统 在编译的时候需要安装许多的工具和库 预置环境 xff1a sudo apt get install g 43 43 libncurses5 dev zlib1g dev bison flex unz
  • 滑动窗口详解

    前言 滑动窗口是双指针的一种特例 xff0c 可以称为左右指针 xff0c 在任意时刻 xff0c 只有一个指针运动 xff0c 而另一个保持静止 滑动窗口路一般用于解决特定的序列中符合条件的连续的子序列的问题 滑动窗口的时间复杂度是线性的
  • RT-Thread入门教程,环境配置和第一个代码

    1 前言 RT Thread这一个操作系统获得很多工程师的好评 xff0c 使用简单 xff0c 支持多 xff0c 有软件包可以下载 xff0c 甚至未来会有更多MicroPython的支持 xff0c 能够兼容主流的一些MCU xff0
  • DHT12温湿度传感器IIC,I2C接口调试心得和代码说明

    来源 xff1a http www fuhome net bbs forum php mod 61 viewthread amp tid 61 2141 DHT11那个单总线的温湿度传感器用的很多了 xff0c aosong推出了DHT12

随机推荐

  • 升级windows11如何在电脑上启用TPM2.0

    本文适用于无法升级到 Windows 11 xff0c 因为他们的电脑当前未启用 TPM 2 0 或其电脑能够运行 TPM 2 0 xff0c 但并未设置为运行 TPM 2 0 1 下载微软电脑健康状况检查 下载地址为 xff1a Wind
  • python调用谷歌翻译

    from GoogleFreeTrans import Translator if name 61 61 39 main 39 translator 61 Translator translator src 61 39 en 39 dest
  • C++(4) 运算符重载

    C 43 43 学习心得 xff08 1 xff09 运算符重载 from 谭浩强 C 43 43 面向对象程序设计 第一版 2014 10 6 4 1什么是运算符重载 用户根据C 43 43 提供的运算符进行重载 xff0c 赋予它们新的
  • C++学习心得(3)多态性与虚函数

    C 43 43 学习心得 xff08 3 xff09 多态性与虚函数 from 谭浩强 C 43 43 面向对象程序设计 第一版 2014 10 13 6 1 多态性的概念 在C 43 43 中 xff0c 多态性是指具有不同功能的函数可以
  • C发送http请求

    C语言发送http请求和普通的socket通讯 原理是一样的 无非就三步connect 连上服务器 send 发送数据 recv 接收数据 只不过发送的数据有特定的格式 下面的是简单发送一个http请求的例子 span class hljs
  • tensorflow(四十七):tensorflow模型持久化

    模型保存 span class token keyword from span tensorflow span class token keyword import span graph util graph def span class
  • git subtree使用

    在一个git项目下引用另一个项目的时 xff0c 我们可以使用 git subtree 使用 git subtree 时 xff0c 主项目下包含子项目的所有代码 使用 git subtree 主要关注以下几个功能 一个项目下如何引入另一个
  • tensorflow(四十八): 使用tensorboard可视化训练出的文本embedding

    对应 tensorflow 1 15版本 log dir span class token operator 61 span span class token string 34 logdir 34 span metadata path s
  • java中数组之间的相互赋值

    前言 本文考虑的研究对象是数组 xff0c 需要明确的是在java中 xff0c 数组是一种对象 xff0c java的所有对象的定义都是放在堆当中的 xff0c 对象变量之间的直接赋值会导致引用地址的一致 在java中声明一个数组 spa
  • tensorflow学习笔记(十):sess.run()

    session run fetch1 fetch2 关于 session run fetch1 fetch2 xff0c 请看http stackoverflow com questions 42407611 how tensorflow
  • tensorflow学习笔记(二十三):variable与get_variable

    Variable tensorflow中有两个关于variable的op xff0c tf Variable 与tf get variable 下面介绍这两个的区别 tf Variable与tf get variable tf Variab
  • pytorch 学习笔记(一)

    pytorch是一个动态的建图的工具 不像Tensorflow那样 xff0c 先建图 xff0c 然后通过feed和run重复执行建好的图 相对来说 xff0c pytorch具有更好的灵活性 编写一个深度网络需要关注的地方是 xff1a
  • pytorch学习笔记(五):保存和加载模型

    span class hljs comment 保存和加载整个模型 span torch save model object span class hljs string 39 model pkl 39 span model 61 torc
  • tensorflow:自定义op简单介绍

    本文只是简单的翻译了 https www tensorflow org extend adding an op 的简单部分 xff0c 高级部分请移步官网 可能需要新定义 c 43 43 operation 的几种情况 xff1a 现有的
  • pytorch学习笔记(十二):详解 Module 类

    Module 是 pytorch 提供的一个基类 xff0c 每次我们要 搭建 自己的神经网络的时候都要继承这个类 xff0c 继承这个类会使得我们 搭建网络的过程变得异常简单 本文主要关注 Module 类的内部是怎么样的 初始化方法中做
  • tensorflow学习笔记(四十五):sess.run(tf.global_variables_initializer()) 做了什么?

    当我们训练自己的神经网络的时候 xff0c 无一例外的就是都会加上一句 sess run tf global variables initializer xff0c 这行代码的官方解释是 初始化模型的参数 那么 xff0c 它到底做了些什么
  • 使用 spacy 进行自然语言处理(一)

    介绍 自然语言处理 NLP 是人工智能方向一个非常重要的研究领域 自然语言处理在很多智能应用中扮演着非常重要的角色 xff0c 例如 xff1a automated chat bots article summarizers multi l
  • 威联通ContainerStation部署Oracle11g

    文章目录 前言部署过程详解使用docker compose文件创建容器临时开启NAS的SSH远程访问通过SSH客户端远程连接NAS进入容器创建用户拷贝容器中的数据库相关文件至宿主机在ContainerStation中修改docker com
  • 十六进制数据与字符串的相互转换

    span class hljs keyword public span span class hljs keyword static span String span class hljs title bytesToHexString sp
  • 贪心算法详解

    前言 贪心算法是动态规划的一种特殊情况 xff0c 需要满足更为苛刻的条件 xff1a 贪心选择 算法种类时间复杂度暴力算法指数级别动态规划多项式级别贪心算法线性级别 什么是贪心选择性质呢 xff0c 简单说就是 xff1a 每一步都做出一