目录
前言
经过前两篇的学习,相信大家对贪心算法的理解逐渐加深。这篇文章将继续完成几道更具复杂性的贪心题目的学习并快速总结解题过程中的关键步骤。希望博主记录的内容能够为大家提供一些实用的思路✍✍✍
K次取反后最大化的数组和
这道题就是给一个整数数组 nums
和一个整数 k
,要求我们经过k
次修改使得数组的和最大并返回这个最大和,所谓的修改就是指将数组中任一数取反🤔🤔🤔
我们来思路梳理
- 为了让数组的和尽可能大,我们应该优先选择绝对值最小的负数进行取反,使得负数变成正数,这样会最大化地增加数组的和🤔 如果所有负数都已经被取反完,但仍有操作次数剩余,那么我们可以通过反复取反最小的绝对值元素来减少损失。如果剩余的
k
是偶数,最终结果不变;如果是奇数,则对最小的绝对值元素再取反一次🤔每次选择取反当前最小的负数(或者最小的绝对值元素),这样可以确保每一步都是局部最优,从而达到全局最优
思路梳理的很清楚了,博主直接给出代码
java
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);//对数组排序便于处理负数和最小绝对值
//遍历数组将负数取分
for(int i = 0; i < nums.length && k > 0; i++){
if(nums[i] < 0){
nums[i] = -nums[i];//取反负数
k--;//每取反一次k减少一次
}
}
//如果k仍大于0则处理剩下的k
if(k % 2 == 1){//奇数则将最小的数取反一次
int minIndex = findMinIndex(nums);
nums[minIndex] = -nums[minIndex];
}
//计算最终和
int sum = 0;
for(int num : nums) sum+= num;
return sum;
}
//找到数组中的最小值索引
private int findMinIndex(int[] nums){
int minIndex = 0;
for(int i = 1; i < nums.length; i++){
if(nums[i] < nums[minIndex]) minIndex = i;
}
return minIndex;
}
}
加油站
这道题就是给出一个加油站数组gas
和一个耗油数组cost
,其中gas[i]
为i加油站能够获取的汽油,而cost[i]
则是指从i加油站到i+1加油站需要消耗的汽油量🤔🤔🤔
我们来思路梳理
- 如果环路上所有加油站提供的油量总和
gas
小于行驶所需的油量总和cost
,则无论从哪里出发都不可能完成一周,直接返回-1
🤔从一个加油站出发,逐步向下一个加油站前进。如果当前加油站的油量不足以开到下一个加油站,就从下一个加油站重新开始尝试🤔 通过局部计算判断从某个加油站开始是否可以行驶一周。如果在某个加油站之前的路段无法到达下一个加油站,则说明从之前的加油站出发都不可能成功,从当前失败的加油站之后开始尝试🤔一旦能绕行一周的加油站找到,则这个解是唯一的
可能逻辑还是不够清楚,我们来进一步梳理贪心四个子逻辑
- 如何判断最优解( 通过累积
total
,判断是否有足够的汽油量来完成一周。如果total
小于 0,则无论从哪个加油站出发,都不可能绕行一圈🤔)
java
int total = 0; // 记录整个环路的总油量差
int tank = 0; // 记录当前从起始站出发时的油量
int start = 0; // 记录当前尝试的起始加油站编号
- 判断局部最优选择的可行性( 对于每个加油站,计算它的油量与到达下一个加油站所需的油量差值
currentFuel
。如果当前油量tank
变成负数,说明从当前起始站无法到达下一个加油站,应该从下一个加油站重新开始🤔)
java
int currentFuel = gas[i] - cost[i]; // 当前加油站的剩余油量
total += currentFuel; // 更新整个环路的总油量差
tank += currentFuel; // 更新当前油箱中的油量
- 更新问题状态( 当
tank < 0
时,重置起始加油站为i + 1
,同时将油箱清零,这意味着我们从新的起始加油站开始尝试🤔)
java
if (tank < 0) {
start = i + 1; // 更新起始加油站为下一个
tank = 0; // 重置油箱
}
完整代码如下
java
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
//初始化总油量、当前油量和起始加油站
int total = 0;
int tank = 0;
int start = 0;
//遍历每个加油站
for(int i = 0; i < gas.length; i++){
int currentFuel = gas[i] - cost[i]; //当前加油站剩余油量
total += currentFuel;//更新整个环路的总油量差
tank += currentFuel;//更新当前邮箱的油量
//判断局部最优选择的可行性
if(tank < 0){
start = i + 1;//重新设置起始加油站
tank = 0;//重置油箱
}
}
return total >= 0? start : -1;//如果总油量差为负则无法完成环路,返回-1
}
}
可能难以理解的部分
- 为什么这里只遍历一次即可
- 我们计算了环路总油量差total,当遍历完加油站后只要环路总油量差不为0说明就是可以绕行一圈的🤔
分发糖果
这道题就是给一个整数数组ratings
表示每个孩子的评分,要求每个孩子都至少分配一个糖果,然后的话相邻评分更高的孩子要获得更多的糖果,然后我们怎么分配的话可能使得分配的糖果数量最小🤔🤔🤔
我们来问题梳理
- 从左到右遍历孩子数组,如果某个孩子的评分比前一个孩子高,那么给他比前一个孩子多一个糖果,这样保证从左往右满足相邻孩子的条件🤔 从右到左再次遍历孩子数组,如果某个孩子的评分比下一个孩子高,并且当前糖果数没有超过下一个孩子的糖果数,那么给他比右边孩子多一个糖果。这样可以确保在从右往左的过程中也满足相邻孩子的要求🤔
逻辑也很清楚,我们直接来编码,完整代码如下
java
class Solution {
public int candy(int[] ratings) {
int n = ratings.length;
int[] candies = new int[n];//存储每个孩子的糖果数
for(int i = 0; i < n; i++)candies[i] = 1; //初始化每个孩子至少有一个糖果
for(int i = 1; i < n; i++){//保证右边评分高的孩子比左边获得更多糖果
if(ratings[i] > ratings[i - 1])candies[i] = candies[i - 1] + 1;
}
for(int i = n - 2; i >= 0; i--){//保证左边评分高的孩子比右边获得更多糖果
if(ratings[i] > ratings[i + 1])candies[i]=Math.max(candies[i], candies[i + 1] + 1); // 保证左边糖果数足够
}
//计算糖果总数
int totalCandies = 0;
for(int candy : candies)totalCandies += candy;
return totalCandies;
}
}
总结
通过这几道贪心算法的题目,我们可以看到,贪心算法的核心在于每一步都选择当前最优解,从而达到全局最优。 希望这些记录分享能帮助大家更好地理解贪心算法✊✊✊