【Java】HOT100 贪心算法

news2024/11/26 10:01:49

目录

理论基础

一、简单贪心

LeetCode455:分发饼干

二、中等贪心

2.1 序列问题

LeetCode376:摆动序列

2.2 贪心股票问题

LeetCode121:买卖股票的最佳时机

LeetCode121:买卖股票的最佳时机ii

2.3 两个维度权衡问题

LeetCode135:分发糖果

三、较难问题

区间问题 

LeetCode55:跳跃游戏i

LeetCode55:跳跃游戏ii

LeetCode452:用最少数量的箭引爆气球

LeetCode435:无重叠区间

LeetCode763:划分字母区间

LeetCode56:合并区间

其他

LeetCode53:最大子序和


贪心算法其实就是没有什么规律可言,所以大家了解贪心算法 就了解它没有规律的本质就够了。 不用花心思去研究其规律, 没有思路就立刻看题解。基本贪心的题目 有两个极端,要不就是特简单,要不就是死活想不出来。  

学完贪心之后再去看动态规划,就会了解贪心和动规的区别。

理论基础

贪心的本质是选择每一阶段的局部最优,从而达到全局最优

举例:有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?指定每次拿最大的,最终结果就是拿走最大数额的钱。

每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优

再举一个例子如果是 有一堆盒子,你有一个背包体积为n,如何把背包尽可能装满,如果还每次选最大的盒子,就不行了。这时候就需要动态规划。动态规划的问题在下一个系列会详细讲解。

什么时候用贪心呢?没有固定的套路。刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心

贪心算法一般分为如下四步:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解


一、简单贪心

LeetCode455:分发饼干

思路:这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,

全局最优就是喂饱尽可能多的小孩

所以可以尝试使用贪心策略,先将饼干数组和小孩数组排序。然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。

class Solution {
    // 思路:优先考虑胃口,先喂饱大胃口
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int count = 0;
        int start = s.length - 1;
        // 遍历胃口
        for (int index = g.length - 1; index >= 0; index--) {
            if(start >= 0 && g[index] <= s[start]) {
                start--;
                count++;
            }
        }
        return count;
    }
}

二、中等贪心

2.1 序列问题

LeetCode376:摆动序列

思路:局部最优是删除连续坡度上的节点,保证这个坡度有两个局部峰值

整体最优是整个序列拥有最多的局部峰值

在实际操作上,实际连删除都不用做,只需添加数组的局部峰值即可

本题的难度在于要考虑多种情况:(curdiff代表后一个坡度,prediff代表前一个坡度

即单调有平坡、上下中间有平坡和只有两个数的情况,具体分析见代码随想录

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if (nums.length <= 1) {
            return nums.length;
        }
        //当前差值
        int curDiff = 0;
        //上一个差值
        int preDiff = 0;
        int count = 1;
        for (int i = 1; i < nums.length; i++) {
            //得到当前差值
            curDiff = nums[i] - nums[i - 1];
            //如果当前差值和上一个差值为一正一负
            //等于0的情况表示初始时的preDiff
            if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
                count++;
                preDiff = curDiff;
            }
        }
        return count;
    }
}

这道题还可以用动态规划来做,这里就不讲了。之后可以试着做一做。


2.2 贪心股票问题

LeetCode121:买卖股票的最佳时机

思路:局部最优即左界最小,在局部最优的基础上要求的全局最优即右界最大,也就是差值最大。

class Solution {
    public int maxProfit(int[] prices) {
        int max = 0;
        int low = Integer.MAX_VALUE;  //保留左侧最小值
        for (int i = 0; i < prices.length; i++) {
            low = Math.min(low, prices[i]);  // 取最左最小价格,确定左界
            max = Math.max(max, prices[i] - low); // 寻找右侧最大值
        }
        return max;
    }
}

LeetCode121:买卖股票的最佳时机ii

思路:局部最优即在区间中寻找所有的最大递增子区间,且子区间之间不能重叠

整体最优:取所有最大的递增子区间,最后利润和也最大。

由于最后只要求利润,该题可以变成只要收集每天的正利润,而无需考虑区间的左右界。

// 贪心思路
class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
        for (int i = 1; i < prices.length; i++) {
            result += Math.max(prices[i] - prices[i - 1], 0);
        }
        return result;
    }
}

2.3 两个维度权衡问题

LeetCode135:分发糖果

思路:本题需要先确定一遍后,在确定另一边,从左到右的局部最优即:只要右边评分比左边大,右边的孩子就多一个糖果。全局最优:评分高的右边孩子比左边孩子糖果更多。

本题需要先从左到右更新,再从右到左,这样才能满足相邻条件。从右到左局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。

class Solution {
    /**
         分两个阶段
         1、起点下标1 从左往右,只要 右边 比 左边 大,右边的糖果=左边 + 1,不大于就取最小糖果数1;
         2、起点下标 ratings.length - 2 从右往左, 只要左边 比 右边 大,此时 左边的糖果应该 取本身的糖果数(符合比它左边大) 和 右边糖果数 + 1 二者的最大值,这样才符合 它比它左边的大,也比它右边大
    */
    public int candy(int[] ratings) {
        int len = ratings.length;
        int[] candyVec = new int[len];
        candyVec[0] = 1;
        for (int i = 1; i < len; i++) {
            candyVec[i] = (ratings[i] > ratings[i - 1]) ? candyVec[i - 1] + 1 : 1;
        }

        for (int i = len - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1]) {
                candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1);
            }
        }

        int ans = 0;
        for (int num : candyVec) {
            ans += num;
        }
        return ans;
    }
}

三、较难问题

区间问题 

LeetCode55:跳跃游戏i

思路:总共要跳nums.length-1步才能到达终点,局部解即取最大跳跃步数max每次循环以第一轮的max为起点再次计算max的覆盖范围

整体解即所有的最大跳跃步数范围是否能覆盖到终点

class Solution {
    public boolean canJump(int[] nums) {
        int max = 0;
        for(int i=0;i<=max;i++){    //这里注意是小于等于max,max更新等于以新一轮覆盖点为起点再次覆盖
            max = Math.max(i+nums[i],max);
            if(max>=nums.length-1)  return true;
        }
        return false;
    }
}

LeetCode55:跳跃游戏ii

思路:局部解即每次的跳转步数尽可能最大,这样跳跃次数就最小

整体解即范围覆盖到终点且跳跃次数最小

本题的关键在于什么时候对步数进行+1?用到两个变量curIndex和preIndex来确定位置

class Solution {
    public int jump(int[] nums) {
        int nextIndex = 0;
        int curIndex = 0;
        int count = 0;
        for(int i=0; i<=nums.length;i++){
            nextIndex = Math.max(nextIndex,i+nums[i]);
            if(i == curIndex){   //当走到当前的最大覆盖范围时,判断是否到终点
                count++;    //如果没到,就要跳一步
                curIndex = nextIndex;   //跳到更新过的下一坐标
                if(nextIndex >= nums.length - 1) break; //如果到了则走完了
            }
        }
        return count;
    }
}

LeetCode452:用最少数量的箭引爆气球

思路:写了435,这道题就很容易了

class Solution {
    public int findMinArrowShots(int[][] points) {
        int count = 1;
        Arrays.sort(points, (x,y)->Integer.compare(x[1],y[1]));
        int end = points[0][1];
        for(int i=1;i<points.length;i++){
            //只要右界排序,记录不重叠的情况就可以了,每次不重叠就需要多一支箭
            if(end < points[i][0]){ 
                count++;
                end = points[i][1];
            }
        }
        return count;
    }
}

LeetCode435:无重叠区间

思路:重叠区间问题通常有两种思路:要么是按左边界排序,要么是按右边界

1. 按右边界升序排列,从左向右可以记录非重叠区间的个数,总数-非重叠个数=重叠个数

非重叠只要判断end和上一区间的左区间的关系

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, (a,b)-> {
            return Integer.compare(a[1],b[1]);
        });
        int count = 1;  //记录非重叠区间的个数
        int end = intervals[0][1];
        for(int i = 1;i < intervals.length;i++){
            if(end <= intervals[i][0]){ //无重叠区间,count++,更新end
                count++;
                end = intervals[i][1];
            }
        }
        return intervals.length-count;
    }
}

2. 按左边界升序排序,向左到右可以记录重叠的区间个数,直接得到重叠个数就是移除个数

非重叠和重叠都要更新end,之所以count取end和右界的较小值是因为,可能会出现>=3个区间重叠的情况

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, (a,b)-> {
            return Integer.compare(a[0],b[0]);
        });
        int count = 0;  //记录重叠区间的个数
        int end = intervals[0][1];
        for(int i = 1;i < intervals.length;i++){
            if(end <= intervals[i][0]){ //无重叠区间,更新end
                end = intervals[i][1];
            }else{
                count++;
                end = Math.min(end, intervals[i][1]);  //重叠区间的end
            }
        }
        return count;
    }
}

LeetCode763:划分字母区间

思路:首先遍历计算每个字母的最后出现下标,然后从头开始遍历,并更新最远出现下标,当到达最后序号时进行一次分割

class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer> list = new ArrayList<>();
        int[] record = new int[26];
        //首先记录字母最后一次出现的下标
        for(int i=0;i<s.length();i++){
            record[s.charAt(i)-'a'] = i;
        }
        int idx = 0;
        int last = -1;
        //然后当遍历下标等于最后一次的下标时,添加字符串长度
        for(int i=0;i<s.length();i++){
            idx = Math.max(idx, record[s.charAt(i)-'a']);
            if(i == idx){
                list.add(idx-last); //先添加结果到list,再更新
                last = idx;
            }
        }
        return list;
    }
}

LeetCode56:合并区间

思路:同样首先按照左边界先排序,然后判断左边界和最右边界

记得判断=到底属于那种情况

class Solution {
    public int[][] merge(int[][] intervals) {
        List<int[]> res = new LinkedList<>();
        Arrays.sort(intervals, (x,y)->Integer.compare(x[0],y[0]));
        int left = intervals[0][0];
        int right = intervals[0][1];
        for(int i = 1; i<intervals.length; i++){
            if(right >= intervals[i][0]){   //重叠,合并
                right = Math.max(right, intervals[i][1]);
            }else{  //不重叠,添加旧区间,更新left、right
                res.add(new int[]{left, right});
                left = intervals[i][0];
                right = intervals[i][1];
            }
        }
        res.add(new int[]{left, right});
        return res.toArray(new int[res.size()][2]);
    }
}

其他

LeetCode53:最大子序和

思路:连续子数组局部最优,即遇到连续和count为负数,则立刻放弃当前count,置0,相当于从当前位置重新开始计算连续子数组和。整体最优即用sum来计算循环中count的最大值。

class Solution {
    public int maxSubArray(int[] nums) {
        if (nums.length == 1){
            return nums[0];
        }
        int sum = Integer.MIN_VALUE;
        int count = 0;
        for (int i = 0; i < nums.length; i++){
            count += nums[i];
            sum = Math.max(sum, count); // 取区间累计的最大值(相当于不断确定最大子序终止位置)
            if (count <= 0){
                count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
            }
        }
       return sum;
    }
}

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

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

相关文章

GitHub Copilot Workspace:欢迎进入原生Copilot开发环境

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

华为Pura70发布,供应链公司进入静默保密期

保密措施&#xff1a;与华为Pura70发布相关的供应链公司在产品发布前后处于静默保密期。这可能是由于华为对于手机供应链的一些信息处于保密状态&#xff0c;尤其是关于麒麟芯片的代工厂商等敏感信息。这种保密措施有助于保持产品的神秘感&#xff0c;调动用户的好奇心&#xf…

mac电脑关于ios端的appium真机自动化测试环境搭建

一、app store 下载xcode,需要登录apple id 再开始下载 二、安装homebrew 1、终端输入命令&#xff1a; curl -fsSL <https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh>如果不能直接安装&#xff0c;而是出现了很多内容&#xff0c;那么这个时候不要着急&…

MFC 列表控件修改实例(源码下载)

1、本程序基于前期我的博客文章《MFC下拉菜单打钩图标存取实例&#xff08;源码下载&#xff09;》 2、程序功能选中列表控件某一项&#xff0c;修改这一项的按钮由禁止变为可用&#xff0c;双击这个按钮弹出对话框可对这一项的记录数据进行修改&#xff0c;点击确定保存修改数…

《R语言与农业数据统计分析及建模》学习——数字图像处理

数字图像处理&#xff08;digital image processing&#xff09;又称计算机图像处理&#xff0c;它是指将图像信号转换成数字信号并利用数字图像处理计算机对其进行处理的过程。 常见的数字图像处理是通过计算机对图像进行去除噪声、增强、复原、分割、提取特征等处理。 R语言…

信息管理与信息系统就业方向及前景分析

信息管理与信息系统(IMIS)专业的就业方向十分广泛&#xff0c;包含计算机方向、企业信息化管理、数据处理和数据分析等&#xff0c;随着大数据、云计算、人工智能、物联网等技术的兴起&#xff0c;对能够处理复杂信息系统的专业人才需求激增&#xff0c;信息管理与信息系统就业…

数据分析:基于DESeq2的转录组功能富集分析

介绍 DESeq2常用于识别差异基因&#xff0c;它主要使用了标准化因子标准化数据&#xff0c;再根据广义线性模型判别组间差异&#xff08;组间残差是否显著判断&#xff09;。在获取差异基因结果后&#xff0c;我们可以进行下一步的富集分析&#xff0c;常用方法有基于在线网站…

Mac 安装 JDK21 流程

一、下载JDK21 访问Oracle官方网站或选择OpenJDK作为替代品。Oracle JDK从11版本开始是商业的&#xff0c;可能需要支付费用。OpenJDK是一个免费开源选项。 Oracle JDK官方网站&#xff1a;Oracle JDK Downloads OpenJDK官方网站&#xff1a;OpenJDK Downloads 这里以JDK21为…

Servlet详解(从xml到注解)

文章目录 概述介绍作用 快速入门Servelt的执行原理执行流程&#xff1a;执行原理 生命周期概述API 服务器启动&#xff0c;立刻加载Servlet对象(理解)实现Servlet方式(三种)实现Servlet接口实现GenericServlet抽象类&#xff0c;只重写service方法实现HttpServlet实现类实现Htt…

uni-app scroll-view隐藏滚动条的小细节 兼容主流浏览器

开端 想写个横向滚动的列表适配浏览器&#xff0c;主要就是隐藏一下滚动条在手机上美观一点。 但是使用uni-app官方文档建议的::-webkit-scrollbar在目标标签时发现没生效。 .scroll-view_H::-webkit-scrollbar{display: none; }解决 F12看了一下&#xff0c;原来编译到浏览…

基于 Wireshark 分析 IP 协议

一、IP 协议 IP&#xff08;Internet Protocol&#xff09;协议是一种网络层协议&#xff0c;它用于在计算机网络中实现数据包的传输和路由。 IP协议的主要功能有&#xff1a; 1. 数据报格式&#xff1a;IP协议将待传输的数据分割成一个个数据包&#xff0c;每个数据包包含有…

Ant Design助力:实现用户列表的优雅展示与管理

文章目录 概要前端讲解登录组件注册组件用户列表组件 后端讲解连接数据库db.js路由routes.jsexpress应用app.js 启动项目小结 概要 在上一篇博客&#x1f6aa;中&#xff0c;我们已经成功实现了登录注册系统的基本功能。现在&#xff0c;我们将进一步完善系统&#xff0c;实现…

第一课 自动驾驶概述

1. contents 2. 什么是无人驾驶/自动驾驶 3 智慧出行大智慧 4. 无人驾驶的发展历程

用户中心(末)

文章目录 开发用户注销前后端后端前端 补充用户注册校验逻辑前后端设计后端前端 后端代码优化封装通用返回对象封装全局异常处理全局请求日志和登录校验 TODO 前端代码优化 开发用户注销前后端 后端 当用户登录成功之后&#xff0c;我们在请求体的 session 中保存了用户的登录…

一对一WebRTC视频通话系列(一)—— 创建页面并显示摄像头画面

本系列博客主要记录WebRtc实现过程中的一些重点&#xff0c;代码全部进行了注释&#xff0c;便于理解WebRTC整体实现。 一、创建html页面 简单添加input、button、video控件的布局。 <html><head><title>WebRTC demo</title></head><h1>…

vue2(4)之scoped解决样式冲突/组件通信/非父子通信/ref和$refs/异步更新/.sync/事件总线/provide和inject

vue2 一、学习目标1.组件的三大组成部分&#xff08;结构/样式/逻辑&#xff09;2.组件通信3.综合案例&#xff1a;小黑记事本&#xff08;组件版&#xff09;4.进阶语法 二、scoped解决样式冲突**1.默认情况**&#xff1a;2.代码演示3.scoped原理4.总结 三、data必须是一个函数…

自动驾驶规划与控制技术解析

目录 1. 自动驾驶技术 2.定位location 3. 地图HD Map ​编辑 4 预测prediction 5 自动驾驶路径规划 6. 自动驾驶路径规划 7. 规划planning 8. 视频路径 1. 自动驾驶技术 2.定位location 3. 地图HD Map 4 预测prediction 5 自动驾驶路径规划 6. 自动驾驶路径规划 7.…

IDEA 2022.1版本开始,可以直接运行Markdown里的命令行

参照这种格式&#xff1a; shell mvn clean install注意idea支持的版本&#xff1a;是从 2022.1版本开始的。 ps&#xff1a;之前有人写过了&#xff0c;感觉很实用但是蛮多开发者不一定会知道的功能。 参考资料&#xff1a; https://www.cnblogs.com/didispace/p/16144107.h…

2.4Java全栈开发前端+后端(全栈工程师进阶之路)-前端框架VUE3-基础-Vue组件

初识Vue组件 Vue中的组件是页面中的一部分&#xff0c;通过层层拼装&#xff0c;最终形成了一个完整的组件。这也是目前前端最流行的开发方 式。下面是Vue3官方给出的一张图&#xff0c;通过图片能清楚的了解到什么是Vue中的组件。 图的左边是一个网页&#xff0c;网页分为了…

革新DNA存储:DNA-QLC编码方案高效可靠,多媒体图像存储新时代启航

在数字信息爆炸的时代&#xff0c;传统存储介质正面临容量、持久性和能耗的极限挑战。为此&#xff0c;大连理工大学计算机科学与技术学院的研究团队在《BMC基因组学》发表了一篇开创性论文&#xff0c;介绍了一种名为DNA-QLC的创新编码方案&#xff0c;为DNA存储系统的高效性和…