134. 加油站
解题思路
这道题的贪心核心有两种思路,重点掌握方法二(全局贪心):
核心逻辑(方法二):
- 总油量判断 :如果所有加油站的总油量
totalGas< 总消耗totalCost,则一定无法绕一圈,直接返回 -1。 - 局部贪心 :遍历每个加油站,维护当前剩余油量
curRest:- 若
curRest + gas[i] - cost[i] < 0,说明从当前起点到第i站无法到达,将起点更新为i+1,并重置curRest为 0。 - 否则,累加
curRest。
- 若
- 最终若总油量足够,起点即为答案(贪心保证了这个起点是唯一可行的)。
完整 Java 代码
java
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int totalGas = 0; // 总油量
int totalCost = 0; // 总消耗
int curRest = 0; // 当前剩余油量
int start = 0; // 起始加油站索引
for (int i = 0; i < gas.length; i++) {
totalGas += gas[i];
totalCost += cost[i];
// 计算从当前起点到i站后的剩余油量
curRest += gas[i] - cost[i];
// 若当前剩余油量<0,说明start到i站无法到达,起点更新为i+1
if (curRest < 0) {
start = i + 1;
curRest = 0; // 重置当前剩余油量
}
}
// 总油量 < 总消耗,直接返回-1
if (totalGas < totalCost) {
return -1;
}
// 题目保证唯一解,返回start
return start;
}
}
代码解析
- 变量初始化 :
totalGas/totalCost统计全局油量和消耗,curRest统计当前起点的剩余油量,start记录候选起点。 - 遍历加油站 :
- 累加总油量和总消耗,判断全局是否可行。
- 计算当前起点到
i站的剩余油量curRest。 - 若
curRest < 0,说明当前起点不可行,更新起点为i+1,并重置curRest(因为从i+1开始重新计算)。
- 全局判断 :若总油量 < 总消耗,返回 -1;否则返回
start(题目保证唯一解)。
135. 分发糖果
解题思路
这道题的贪心核心是:分两次遍历,先处理一边再处理另一边,不两头兼顾:
- 第一次遍历(从左到右):保证每个孩子如果比左边孩子评分高,糖果数比左边多 1(满足 "左规则")。
- 第二次遍历(从右到左):保证每个孩子如果比右边孩子评分高,糖果数取 "当前值" 和 "右边孩子糖果数 + 1" 的最大值(满足 "右规则")。
- 最终累加所有糖果数即为答案。
完整 Java 代码
java
class Solution {
public int candy(int[] ratings) {
int n = ratings.length;
int[] candies = new int[n];
// 步骤1:初始化每个孩子至少1颗糖果
for (int i = 0; i < n; i++) {
candies[i] = 1;
}
// 步骤2:从左到右遍历,处理左规则
for (int i = 1; i < n; i++) {
// 如果当前孩子评分 > 左边,糖果数=左边+1
if (ratings[i] > ratings[i-1]) {
candies[i] = candies[i-1] + 1;
}
}
// 步骤3:从右到左遍历,处理右规则
for (int i = n-2; i >= 0; i--) {
// 如果当前孩子评分 > 右边,取当前值和右边+1的最大值
if (ratings[i] > ratings[i+1]) {
candies[i] = Math.max(candies[i], candies[i+1] + 1);
}
}
// 步骤4:累加所有糖果数
int total = 0;
for (int num : candies) {
total += num;
}
return total;
}
}
代码解析
- 初始化 :创建与评分数组等长的
candies数组,每个元素初始化为 1(每个孩子至少 1 颗糖果)。 - 左到右遍历:从索引 1 开始,若当前孩子评分 > 左边,糖果数 = 左边糖果数 + 1(保证左规则)。
- 右到左遍历 :从索引
n-2开始,若当前孩子评分 > 右边,糖果数取 "当前值" 和 "右边 + 1" 的最大值(既满足左规则,又满足右规则)。 - 累加求和 :遍历
candies数组,累加得到总糖果数。
860. 柠檬水找零
解题思路
这道题的贪心核心是:优先用大面额找零,减少小面额的消耗(因为 10 元只能找零 20 元,而 5 元能找零 10/20 元,优先级更高):
- 维护两个变量:
count5(5 元数量)、count10(10 元数量)(20 元无需统计,因为无法找零)。 - 遍历账单数组:
- 收到 5 元:
count5++。 - 收到 10 元:需要找零 5 元,若
count5 > 0,则count5--、count10++;否则返回 false。 - 收到 20 元:优先用 10+5 找零(贪心),若没有则用 3 张 5 元;都没有则返回 false。
- 收到 5 元:
- 遍历结束后返回 true。
完整 Java 代码
java
class Solution {
public boolean lemonadeChange(int[] bills) {
int count5 = 0; // 5元纸币数量
int count10 = 0; // 10元纸币数量
for (int bill : bills) {
if (bill == 5) {
// 收到5元,无需找零,直接计数
count5++;
} else if (bill == 10) {
// 收到10元,需要找零5元
if (count5 > 0) {
count5--;
count10++;
} else {
return false;
}
} else if (bill == 20) {
// 收到20元,优先用10+5找零(贪心)
if (count10 > 0 && count5 > 0) {
count10--;
count5--;
} else if (count5 >= 3) {
// 没有10元,用3张5元
count5 -= 3;
} else {
return false;
}
}
}
return true;
}
}
代码解析
- 变量初始化 :
count5和count10初始化为 0,分别记录 5 元、10 元的数量。 - 遍历账单 :
- 5 元:直接计数,无需找零。
- 10 元:必须找零 5 元,若没有则返回 false;否则更新计数。
- 20 元:贪心优先用 10+5 找零(保留更多 5 元应对后续 10 元找零),若不行则用 3 张 5 元,否则返回 false。
- 返回结果:遍历完成后说明所有账单都能找零,返回 true。
406. 根据身高重建队列
解题思路
这道题的贪心核心是:先处理高个子,再处理矮个子,不两头兼顾:
- 排序:将数组按「身高降序、k 值升序」排序(高个子先排,身高相同则 k 小的先排)。
- 插入 :遍历排序后的数组,按每个元素的
k值(前面比自己高 / 等高的人数)插入到结果列表的对应位置。- 因为高个子先排,插入矮个子时,不会影响高个子的
k值(矮个子不参与高个子的计数)。
- 因为高个子先排,插入矮个子时,不会影响高个子的
完整 Java 代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public int[][] reconstructQueue(int[][] people) {
// 步骤1:排序:身高降序,k值升序
Arrays.sort(people, (a, b) -> {
if (a[0] != b[0]) {
return b[0] - a[0]; // 身高降序
} else {
return a[1] - b[1]; // k值升序
}
});
// 步骤2:按k值插入到对应位置
List<int[]> queue = new ArrayList<>();
for (int[] p : people) {
queue.add(p[1], p); // 插入到索引为p[1]的位置
}
// 步骤3:转换为数组返回
return queue.toArray(new int[queue.size()][]);
}
}
代码解析
- 排序规则 :
- 身高不同:降序排列(高个子先处理)。
- 身高相同:k 值升序排列(k 小的先排,避免插入时影响)。
- 插入逻辑 :
- 用
ArrayList存储结果,因为其add(index, element)方法可以高效插入。 - 遍历排序后的数组,将每个人插入到索引为
p[1]的位置(p[1]表示前面需要有p[1]个比他高 / 等高的人,而此时列表中都是更高 / 等高的人,插入位置正好满足要求)。
- 用
- 结果转换 :将
ArrayList转换为二维数组返回。
总结
- 加油站:贪心核心是 "全局判断总油量是否足够,局部重置起点",总油量足够时,第一个能走完局部的起点就是全局解。
- 分发糖果:贪心核心是 "分两次遍历,先左后右(或先右后左)",避免同时兼顾两边导致逻辑混乱。
- 柠檬水找零:贪心核心是 "优先用大面额找零",保留更多小面额应对后续需求。
- 根据身高重建队列:贪心核心是 "先处理高个子,再按 k 值插入矮个子",高个子的位置不受矮个子影响。