贪心算法(局部最优实现全局最优)第一篇

贪心算法(局部最优实现全局最优)第一篇

目录

1. 什么是贪心算法

2. 贪心算法的解题步骤

3. 具体例题及代码

3.1 LeetCode860. 柠檬水找零

3.2 LeetCode2208. 将数组和减半的最少操作次数

3.3 LeetCode179. 最大数


从这篇文章开始,我们开始讲解贪心算法。

1. 什么是贪心算法

贪心算法是算法设计中的经典思想,核心逻辑用一句话就能概括 ——每一步都做出当前情况下的最优选择,不回头、不纠结,最终期望得到全局最优解。它不像动态规划那样依赖中间状态存储,也不用回溯尝试所有可能,凭借 “直来直往” 的思路,成为解决特定问题的高效方案。

2. 贪心算法的解题步骤

  1. 问题拆解:将复杂问题拆分成多个连续的局部决策步骤。
  2. 确定贪心策略:明确每一步的 “最优标准”(比如 “选最小”“选最大”“选最早结束”)。
  3. 验证可行性:确认该策略能满足 “局部最优推全局最优”,避免无效贪心
  4. 代码实现:通常先排序(按策略对应的规则),再遍历执行贪心选择。

之所以给第三步加粗,是因为这一步在我看来是最重要的。因为有些题目看起来好像可以使用贪心,但是实际上是不可以的,很多时候局部最优达不到全局最优解。

同时在我看来它也没有具体的模版,因为它要根据每一题来设计不同的贪心思路。我觉得贪心最重要的就是经验。

3. 具体例题及代码

3.1 LeetCode860. 柠檬水找零

我们看下面这张图片,要求是模拟实现找零,那么我们的策略就是每一次都尽量给大的零钱,因为这样我们可以确保只会出现零钱不够的情况,不会出现有钱但是无法使用的情况。

所以代码设计也很简单,就是找零能用大的就用大的,就这样设计就好了。在我们一开始对于贪心算法的学习过程中我觉得最重要的就是我们可以想到使用贪心算法。

其实这道题的代码也很好写,就是几个条件判断就好了。

class Solution { public: bool lemonadeChange(vector<int>& bills) { int a=0;//5 int b=0;//10 int c=0;//20 int sz=bills.size(); for(int i=0;i<sz;++i) { int num=bills[i]; if(num==20) { if(b&&a) { b--; a--; c++; } else if(a>3||a==3) { a-=3; c++; } else return false; } if(num==10) { if(a) { a--; b++; } else return false; } if(num==5) a++; } return true; } };

3.2 LeetCode2208. 将数组和减半的最少操作次数

我们看下面这道题,题目要求我们最小次数的对数组里面的数除以2,使数组和小于等于原先数组的一半。其实从题目上我们很好想到,就是每次找数组里面最大的数,然后给它减去一半。

我们看下面这个代码,其实我们只要知道priority_queue就可以很快的做出来这道题,priority_queue是默认大堆的,所以我们在这里就不用更改。同时它每插入一个数都会对其进行排序,所以我们只要不断的取出堆顶的元素就好了。

class Solution { public: int halveArray(vector<int>& nums) { priority_queue<double> p; double a1=0; double a2=0; for(auto& a:nums) { a1+=a; a2+=a; p.push(a); } a1/=2; int mem=0; while(a1<a2) { double tmp=p.top()/2; p.pop(); a2-=tmp; p.push(tmp); mem++; } return mem; } };

3.3 LeetCode179. 最大数

题目的意思就是给我们一个数组,然后我们要用数组里面的数来组成一个最大的数。所以我们根据组合后数字的大小来排序。

我们看代码,因为知道是通过组合后数字的大小来排序,所以我们在这里就直接通过一个lambda 表达式,给sort重写排序规则,就可以做出来这道题了。

class Solution { public: string largestNumber(vector<int>& nums) { vector<string> str; for(auto& s:nums) str.push_back(to_string(s)); sort(str.begin(),str.end(),[](const string& s1,const string&s2){ return s1+s2>s2+s1; }); string tmp; for(auto& s:str) tmp+=s; if(tmp[0]=='0') return "0"; else return tmp; } };

Read more

爆肝 2 天,用 GLM5 开发了 OpenClaw 接入微信 bot,已开源!

这是苍何的第 493 篇原创! 大家好,我是苍何。 OpenClaw,这个 GitHub 上 18 万 Star 的怪物级开源项目,你们应该都听过了吧? 飞书能接、钉钉能接、企业微信能接、QQ 能接、Discord 能接…… 但偏偏最多人用的「微信个人号」,它不支持。 我翻遍了 GitHub、掘金、知乎,找到的方案要么是企业微信绕一圈,要么是用微信 Web 协议搞,动不动就封号。 说实话,这谁顶得住? 天天在微信上跟朋友聊天、在群里吹水,结果想接个 OpenClaw 都这么费劲? 麻了。 于是我决定自己干。 「爆肝 2 天,我把 OpenClaw 接入了微信个人号,并且已经开源了。」 地址:

By Ne0inhk
Git 用户名与邮箱配置指南

Git 用户名与邮箱配置指南

前言 在使用 Git 进行版本控制时,每一次代码提交(commit)都会记录提交者的身份信息。这些信息不仅用于追踪代码变更历史,还在团队协作、代码审查和开源贡献中发挥着重要作用。 Git 通过 用户名(user.name) 和 邮箱(user.email) 来标识开发者身份。正确配置这两项信息,是使用 Git 的第一步,也是确保提交记录清晰、可追溯的关键。 一、为什么需要设置用户名和邮箱? Git 是一个分布式版本控制系统,它不依赖中央服务器来管理用户身份。因此,每个开发者必须在本地明确声明自己的身份。Git 会在每次执行 git commit 时,自动将 user.name 和 user.email 写入提交记录。 如果没有正确设置,可能会导致: * 提交记录显示为 unknown 或默认系统用户名;

By Ne0inhk

超详细 Git 讲解(通俗易懂 + 全面覆盖)

超详细 Git 讲解(通俗易懂 + 全面覆盖) 一、先搞懂:为什么需要 Git?(5 分钟) 先从大一同学能理解的场景切入,避免一上来就讲技术: * 场景 1:写代码改来改去,想回退到昨天的版本,却找不到旧文件; * 场景 2:实验室多人合作写项目,改同一个文件互相覆盖,代码越改越乱; * 场景 3:想同时开发两个功能(比如 “登录功能” 和 “注册功能”),改了登录的代码,注册的代码就没法测试。 Git 的核心作用:版本控制 + 多人协作,解决以上所有问题 —— 它就像代码的 “时光机”+“协作神器”,能记录每一次修改,还能让多人并行开发不冲突。 二、Git 核心概念:先把 “地基” 打牢(15 分钟)

By Ne0inhk
Git 使用技巧——查看 Commit 修改文件的概要

Git 使用技巧——查看 Commit 修改文件的概要

Git 使用技巧——查看 Commit 修改文件的概要 在日常 Git 版本管理中,经常需要查询某个 Commit 修改了哪些文件,甚至每个文件的增删行数统计,本文整理了多种实用方法,覆盖不同使用场景,满足从「简洁文件列表」到「详细行数统计」的各类需求。 一、前置准备:获取 Commit ID 所有操作都需要先获取目标 Commit 的 commit-id,commit-id 是一串 40 位的哈希值,Git 支持简写前 6-8 位使用,获取方式如下: # 简洁格式查看提交历史(优先推荐,输出包含 简写commit-id + 提交说明) git log --oneline # 完整格式查看提交历史(包含完整 commit-id、作者、时间、

By Ne0inhk