技术积累算法中的贪心算法
Posted TianYou
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了技术积累算法中的贪心算法相关的知识,希望对你有一定的参考价值。
贪心算法是什么
贪心算法是一种常见的算法思想,主要应用于优化问题中,特别是在计算机科学和运筹学领域中。贪心算法的核心思想是每一步都选择当前最好的选项,从而得到全局最优解。
贪心算法通常包括以下步骤:
-
确定问题的最优子结构:即在问题中寻找那些可以自行解决的子问题。
-
开始构建解决方案:从问题的初始状态开始,按照某种规则选择一个最优解,并将其添加到中间方案中。该步骤不断重复,直到找到全局最优解。
-
判断可行性:为了确保得到一个全局最优解,需要在每个构建解决方案的步骤中,检查得到的局部最优解是否是可行的。如果当前的局部最优解无法满足问题的限制条件,则需要放弃此局部最优解,重新开始构建方案。
贪心算法的优点是输入数据越大,运行时间越短;同时,由于贪心算法的设计都是局部的最优决策,不是全局的最优决策,因此可能不会得到最优解,但通常会得到接近最优解的解决方案。
贪心算法适用于一些特殊的算法场景,如图论中的最小生成树算法、哈夫曼编码等。同时,在一些工业设计、物流计划及经济学领域中也有应用。
贪心算法需要注意的问题是不能保证一定得到全局最优解,有可能会导致次优解的出现。因此,在具体应用中,需要充分了解问题的性质,深入分析问题才能设计出较好的贪心算法。
旅行商问题
一个旅行商要拜访n个城市,求他走的最短路径。
解题思路:
- 随意选择一个城市作为起点
- 从该城市出发,依次经过还未访问的最近的城市
- 计算路径长度,并记录已访问的城市
- 重复步骤2-3,直到所有城市都被访问
- 返回起点城市,路径长度即为最短路径
// cities为城市数量,dist为城市间距离矩阵
function TSP (cities, dist)
visited = [false] * cities // 初始化所有城市未被访问
current_city = 0 // 从城市0开始
visited[current_city] = true // 标记当前城市为已访问
path = [current_city] // 记录遍历路径
total_distance = 0 // 路径总距离
while true:
if len(path) == cities: // 若所有城市都已访问过,则返回起点城市并计算路径总距离
total_distance += dist[current_city][0] // 加上最后一个城市到起点城市的距离
path.append(0)
return path, total_distance
next_city = -1 // 下一个要访问的城市
min_distance = Inf // 到下一个城市路径的最小距离
for i in range(cities):
if not visited[i] and dist[current_city][i] < min_distance:
next_city = i
min_distance = dist[current_city][i]
current_city = next_city // 更新当前城市
visited[current_city] = true // 标记新城市为已访问
path.append(current_city) // 记录经过的城市
total_distance += min_distance // 累计最小距离
部分背包问题
有n个物品和一个容量为C的背包,每个物品都有自己的价值和重量,求装入背包的物品的最大价值。
1.计算每个物品的性价比(价值/重量)。
2.将物品按性价比从高到低排序。
3.从性价比最高的物品开始,依次放入背包,直到背包装满或所有物品都放入背包。
function fractional_knapsack(n, item, C)
// n表示物品数量,item为物品数组,C为背包容量
for i from 1 to n do
item[i].ratio = item[i].value / item[i].weight
// 计算每个物品的性价比
sort item by decreasing ratio
// 将物品按性价比从高到低排序
total_value = 0
for i from 1 to n do
if C >= item[i].weight then
total_value += item[i].value
C -= item[i].weight
// 如果背包容量可以放下物品i,则将物品i完全放入背包
else
total_value += C * item[i].ratio
break
// 否则将物品i按比例分割,在背包中放入一部分
// 直到背包装满或物品i全部放入
return total_value
// 返回装入背包的物品的最大价值
区间调度问题
给定n个区间,求用尽可能少的区间覆盖整个区间的最大数量。
- 首先按照区间结束时间的顺序将所有区间排序(从小到大),设排序后的区间序列为intervals。
- 初始化变量end为区间intervals[0]的结束时间,计数器count为1,表示第一个区间一定要选。
- 遍历排序后的区间序列intervals,如果当前区间的开始时间大于等于end,则选择该区间,将end更新为该区间的结束时间,计数器count加1。
- 最后输出计数器count即为最大数量。
sort(intervals) // 对区间按照结束时间进行排序
end = intervals[0].end // 初始化end为第一个区间的结束时间
count = 1 // 初始化计数器count为1
for i in range(1, intervals.size()):
if intervals[i].start >= end:
count += 1
end = intervals[i].end
print(count) // 输出最大数量
最小罚款问题
某市道路有n个路口需要维修,第i个路口在时间ti - li到ti + li之间维修,若在该时段经过会被罚款wi。求如何安排维修时间,使得罚款总额最小。
- 将每个路口按照维修起始时间递增排序
- 遍历所有路口,维护一个区间集合,表示当前需要维修的路口时间段
- 对于每个路口,如果它的维修时间段与当前区间集合存在交集,则将交集部分取出,并且计算该部分的罚款总额
- 将该路口的维修时间段加入当前区间集合,维护集合的增序,重复步骤3,直至处理完所有路口
# 将每个路口按照维修起始时间递增排序
sorted_intervals = sorted(intervals, key=lambda x: x[0])
# 初始:空集合 s,罚款总额 total = 0
s = set()
total = 0
# 遍历所有路口
for interval in sorted_intervals:
# 维修时间段 [ti-li, ti+li] 表示为区间 [l,r]
l, r, w = interval
# 逐个处理当前区间集合中的所有区间
remove_intervals = set()
for i in s:
# 计算 区间 interval 与 i 的交集
a, b = max(l, i[0]), min(r, i[1])
if a <= b:
# 将交集 [a,b] 内的路口从集合 s 中删除
remove_intervals.add(i)
# 将交集内的罚款总额加入 total
total += w * (b - a + 1)
# 从集合 s 中删除所有交集区间
s -= remove_intervals
# 将区间 [l,r] 加入集合 s
s.add((l, r))
# 对于集合 s 中所有区间,以左端点为第一关键字,右端点为第二关键字进行排序
s = sorted(s, key=lambda x: (x[0], x[1]))
# 返回罚款总额
return total
跳跃游戏
给定一个数组,数组中的每个元素代表你在该位置可以跳跃的最大长度,求是否可以到达最后一个元素。
-
记录一个变量max_reach表示当前所能到达的最远距离,初始值为第一个元素的距离。
-
对数组从第二个元素开始遍历: a. 如果当前位置超出了max_reach的范围,则说明无法到达最后一个元素,返回false。 b. 否则,将当前位置能到达的最远距离和max_reach取最大值,更新max_reach。
-
遍历结束后,如果max_reach能够到达最后一个元素,则返回true;否则,返回false。
function canJump(nums)
let max_reach = nums[0];
for (let i = 1; i < nums.length; i++)
if (i > max_reach)
return false;
max_reach = Math.max(max_reach, i + nums[i]);
return max_reach >= nums.length - 1;
化学物质混合问题
有n种化学物质,需要混合制成一种新的化学物质,各种化学物质有自己的份量和价格,求最小的制作成本。
-
首先将各种化学物质按价格从小到大排序。
-
然后从价格最低的化学物质开始,依次按其份量的比例将其混合到目标物质中。
-
如果已混入的各种化学物质份量之和等于目标物质的总份量,则制作完成;否则继续将价格次低的化学物质混入。
-
直到制作完成或者所有化学物质都已混入为止。
// 输入:
// chemicals: 化学物质数组,包括每种物质的份量和价格
// target_amount: 目标物质的总份量
// 注:代码中的by_price为排序关键字,需要根据具体实现进行定义。
function mixedChemicals(chemicals[], target_amount):
// 按价格从小到大排序
sort(chemicals, by_price)
i = 0 // 当前混入的化学物质下标
total = 0 // 已混入的各种化学物质总份量之和
cost = 0 // 制作成本
// 按比例依次混入各种化学物质
while (total < target_amount) and (i < len(chemicals)):
// 每次混入化学物质的份量
amount = min(target_amount - total, chemicals[i].amount)
// 每次混入的成本
unit_cost = chemicals[i].price / chemicals[i].amount
// 更新总成本
cost += amount * unit_cost
// 更新已混入的总份量
total += amount
// 更新当前混入的化学物质下标
i += 1
// 判断是否制作成功
if total == target_amount:
return cost
else:
return \'制作失败\'
资源分配问题
- 给定n个资源和m个任务,每个任务需要一定量的资源,其中一些任务是必须完成的,如何分配资源使得完成必须任务的代价最小。
- 将所有任务按是否为必须任务分成两组:必须完成的任务和非必须任务。
- 对必须完成的任务按照所需资源从大到小排序。
- 从资源数最大的必须任务开始,依次分配资源,直到分配完毕或无法完成必须任务。
- 对剩余的非必须任务按照所需资源从大到小排序。
- 依次给非必须任务分配资源,直到分配完毕或无法完成任务。
//将所有任务按是否为必须任务分成两组:必须完成的任务和非必须任务。
for each task:
if task is mandatory:
add task to mandatory_tasks
else:
add task to optional_tasks
//对必须完成的任务按照所需资源从大到小排序。
sort(mandatory_tasks, by resource needed, descending)
//从资源数最大的必须任务开始,依次分配资源,直到分配完毕或无法完成必须任务。
for each task in mandatory_tasks:
if task can be completed:
allocate resources to task
else:
break
//对剩余的非必须任务按照所需资源从大到小排序。
sort(optional_tasks, by resource needed, descending)
//依次给非必须任务分配资源,直到分配完毕或无法完成任务。
for each task in optional_tasks:
if task can be completed:
allocate resources to task
else:
break
算法笔记贪心算法
前言: 对于贪心算法的学习主要以增加阅历和经验为主,也就是多做多积累经验,以下通过几个题目来介绍贪心算法的乐趣!
文章目录
- 1. 贪心算法基本介绍
- 2. 题目
- 题一:给定一个由字符串组成的数组 strs,必须把所有的字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果
- 题二:一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。给你每一个项目的开始时间和结束时间,你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。返回最多的宣讲场次
- 题三:一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管怎么切,都需要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?输入一个数组,返回分割的最小代价
- 题四:输入:正数数组 costs、正数数组 profits、正数 K、正数 M。cost[i] 表示 i 号项目的花费,profits[i] 表示 i 号项目在扣除花费之后还能挣到的钱,K 表示你只能串行的最多做 K 个项目,M 表示你初始的资金。说明:你没做完一个项目,马上获得的收益,可以支持你去做下一个项目。不能并行的做项目。输出:你最后获得的最大钱数
- 题五: 给定一个字符串 str,只由 'X' 和 '.' 两种字符构成。'X' 表示墙,不能放灯,也不需要点亮;'.' 表示居民点,可以放灯,需要点亮。如果灯放在 i 位置,可以让 i-1、i 和 i+1 三个位置被点亮。返回如果点亮 str 中所有需要点亮的位置,至少需要几盏灯
1. 贪心算法基本介绍
- 是一种局部最功利的标准,总是做出在当前看来是最好的选择
- 难点在于证明局部最功利的标准可以得到全局最优解
- 贪心一般跟排序和堆有关
- 贪心算法的难点在于如何证明该标准可行,但是证明往往较难,可以通过对数器来验证最终的方式是否可行
2. 题目
题一:给定一个由字符串组成的数组 strs,必须把所有的字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果
方法模板: 为了得到字典序最小的结果,我们可以将数组进行排序,使排序后的数组拼接的字符串的字典序最小即可。排序规则为字符串 a 和 字符串 b进行 a + b
和 b + a
的拼接后比比较这两者的字典序大小,如果 a + b
小,则字符串 a 排在 字符串 b 前。
public static class MyComparator implements Comparator<String>
@Override
public int compare(String o1, String o2)
return (o1 + o2).compareTo(o2 + o1);
public static String lowestString(String[] strs)
if (strs == null || strs.length == 0)
return "";
Arrays.sort(strs, new MyComparator());
String ans = "";
for (int i = 0; i < strs.length; i++)
ans += strs[i];
return ans;
示例代码:
public static void main(String[] args)
String[] strs = "ba", "b", "cda";
System.out.println(lowestString(strs));
// 结果为:babcda
题二:一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。给你每一个项目的开始时间和结束时间,你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。返回最多的宣讲场次
结束时间最早
方法模板: 当我们思考该问题时,我们可能会想着使用开始时间最早或者宣讲时间最短来进行验证,但是这种贪心思路是错误的,正确的方法是以结束时间最早来思考这道题。找到第一个结束时间最早的会议,然后删除其余开始时间早于最早结束时间的会议,然后再找第二个结束时间最早的会议…
public static class Program
public int begin;
public int end;
public Program(int begin, int end)
this.begin = begin;
this.end = end;
public static int bestArrange(Program[] programs)
Arrays.sort(programs, new MyComparator());
int timeLine = 0;
int count = 0;
for (int i = 0; i < programs.length; i++)
if (programs[i].begin >= timeLine)
count++;
timeLine = programs[i].end;
return count;
public static class MyComparator implements Comparator<Program>
@Override
public int compare(Program o1, Program o2)
return o1.end - o2.end;
示例代码:
public static void main(String[] args)
Program[] programs = new Program(1, 2), new Program(1, 5), new Program(3, 4), new Program(5, 6) ;
System.out.println(bestArrange(programs));
// 结果为:3
题三:一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管怎么切,都需要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?输入一个数组,返回分割的最小代价
举例:
例如,给定数组10, 20, 30,代表一共三个人,整块金条长度为60,要将金条分成10,20,30三个部分。
如果先把长度60的金条分成10和50,花费60;再把长度50的金条分成20和30,花费50,一共花费110的铜板
如果先把长度60的金条分成30和30,花费60,再把长度30的金条分成30和10,花费30,一共花费90铜板
方法模板: 思考该题时可能会想到以最大部分的金条长度来切割,但是该贪心思路是错误的(如要分割成长度为97、98、99、100几部分,则要先将金条分成 97+98 和 99+100 两部分)。这里可以建一个小根堆,然后依次弹出堆顶两个元素相加,相加的结果就是这两块金条分割的花费,然后再将该结果入堆,继续以上步骤,直到堆中只剩一个数值
public static int lessMoney(int[] arr)
Queue<Integer> queue = new PriorityQueue<>();
for(int num : arr)
queue.add(num);
int money = 0;
int cur = 0;
while(queue.size() > 1)
cur = queue.poll() + queue.poll();
money += cur;
queue.add(cur);
return money;
示例代码:
public static void main(String[] args)
int[] arr = 10, 20, 30;
System.out.println(lessMoney(arr));
// 结果为:90
题四:输入:正数数组 costs、正数数组 profits、正数 K、正数 M。cost[i] 表示 i 号项目的花费,profits[i] 表示 i 号项目在扣除花费之后还能挣到的钱,K 表示你只能串行的最多做 K 个项目,M 表示你初始的资金。说明:你没做完一个项目,马上获得的收益,可以支持你去做下一个项目。不能并行的做项目。输出:你最后获得的最大钱数
方法模板: 可以构建一个以项目的花费从低到高的小根堆,并将所有项目入堆,再构建一个以项目的利润从高到低的大根堆,将小根堆中消费少于当前拥有的资金的项目弹出,并加入到大根堆中,取利润最高的项目进行购买,之后依次进行以上步骤
public static class Program
public int cost;
public int profit;
public Program(int cost, int profit)
this.cost = cost;
this.profit = profit;
public static class MinCostComparator implements Comparator<Program>
@Override
public int compare(Program o1, Program o2)
return o1.cost - o2.cost;
public static class MaxProfitComparator implements Comparator<Program>
@Override
public int compare(Program o1, Program o2)
return o2.profit - o1.profit;
public static int findMaximizedCapital(int K, int W, int[] profits, int[] capital)
Queue<Program> minCostQ = new PriorityQueue<>(new MinCostComparator());
Queue<Program> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
for(int i = 0; i < capital.length; i++)
Program program = new Program(capital[i], profits[i]);
minCostQ.add(program);
while(K > 0)
while(minCostQ.size() > 0 && minCostQ.peek().cost <= W)
maxProfitQ.add(minCostQ.poll());
if(maxProfitQ.isEmpty())
break;
W += maxProfitQ.poll().profit;
K--;
return W;
示例代码:
public static void main(String[] args)
int[] capital = 2,1,6,4;
int[] profits = 2,3,1,4;
int K = 3;
int W = 1;
System.out.println(findMaximizedCapital(K,W,profits,capital));
// 结果为:10
题五: 给定一个字符串 str,只由 ‘X’ 和 ‘.’ 两种字符构成。‘X’ 表示墙,不能放灯,也不需要点亮;’.’ 表示居民点,可以放灯,需要点亮。如果灯放在 i 位置,可以让 i-1、i 和 i+1 三个位置被点亮。返回如果点亮 str 中所有需要点亮的位置,至少需要几盏灯
方法模板: 假设 index 位置是墙时,那么 index = index+1;当 index 位置是居民点时,index+1 位置为墙时,就需要一盏灯,并且 index = index+2;当 index 位置为居民点,index+1 位置为居民点,不论 index+2 位置为居民点还是墙,都需要一盏灯,并且 index = index+3
public static int minLight(String road)
char[] str = road.toCharArray();
int index = 0;
int light = 0;
while(index<str.length)
if(str[index] == 'X')
index++;
else
light++;
if(index+1==str.length)
break;
else
if(str[index+1]=='X')
index=index+2;
else
index=index+3;
return light;
示例代码:
public static void main(String[] args)
String road = "X.XX..X...X....X";
System.out.println(minLight(road));
// 结果为:5
以上是关于技术积累算法中的贪心算法的主要内容,如果未能解决你的问题,请参考以下文章