贪心算法
Dec 28, 2025
Updated on Jan 1, 2026
引言
贪心算法(Greedy Algorithm)是在每一步都作出当前看起来最优的选择(局部最优),希望最终得到全局最优解的方法。
它的特点是:
- 不回溯,不全局搜索
- 一次决策影响后续,但不重新考虑之前的选择
- 快、实现简单、但并非对所有问题都适用
贪心算法通过在每一步做出当前最优(局部最优)的选择来构建整体解,要求问题满足贪心选择性质与最优子结构。它通常以“排序 + 单次决策”的形式出现,优点是简单高效,缺点是只对结构良好的问题有效,不能解决可以试试回溯或动态规划。
题目
1. 分发饼干
LT.455. Assign Cookies
局部策略:
每次用当前能用的最小饼干去喂当前胃口最小的孩子。
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int i = 0; int j = 0;
while (i < g.length && j < s.length) {
if (s[j] >= g[i]) {
i++; }
j++; }
return i;
}
}
2. 摆动序列
LT.376. Wiggle Subsequence
长度 = 方向变化次数 + 1
- 每发生一次“上升 ⇄ 下降”的切换,就新增了一个“拐点”
- 而拐点恰好就是摆动序列中必须保留的点
- 拐点数量 + 初始点 = wiggle 长度
class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length < 2) {
return nums.length;
}
int prevDiff = 0;
int count = 0;
for (int i = 1; i < nums.length; i++) {
int diff = nums[i] - nums[i - 1];
if ((diff > 0 && prevDiff <= 0) || (diff < 0 && prevDiff >= 0)) {
count++;
prevDiff = diff;
}
}
return count + 1;
}
}
3. 最大子序和
LT.53. Maximum Subarray
当前和一旦变负就清零重开;一路过程中记下遇到过的最大值。
- 我们维护一个当前连续子数组的最大和
curSum,以及一个全局最大 maxSum
- 当
curSum < 0 时,它对后面的子数组只会产生负贡献,立刻丢掉,从当前元素重新开始
- 否则把当前元素接上,继续累加
- 遍历过程中不断更新
maxSum
class Solution {
public int maxSubArray(int[] nums) {
int maxSum = nums[0]; int curSum = nums[0];
for (int i = 1; i < nums.length; i++) {
curSum = Math.max(nums[i], curSum + nums[i]);
maxSum = Math.max(curSum, maxSum);
}
return maxSum;
}
}
4. 买卖股票的最佳时机 II
LT.122. Best Time to Buy and Sell Stock II
只要今天价格高于昨天,就把这段正差价累加起来,因为每一段上升都可以视为一次独立的买入卖出盈利。
p[3] - p[0] = (p[3] - p[2]) + (p[2] - p[1]) + (p[1] - p[0])
| + take | | - ignore | | + take |
class Solution {
public int maxProfit(int[] prices) {
if (prices.length < 2) {
return 0;
}
int profit = 0;
for (int i = 1; i < prices.length; i++) {
profit += Math.max(prices[i] - prices[i - 1], 0);
}
return profit;
}
}
5. 跳跃游戏
LT.55. Jump Game
从左到右维护当前能到达的最远位置,只要当前位置不超过这个最远值,并不断更新它,最后能覆盖到最后一个下标就成功。
class Solution {
public boolean canJump(int[] nums) {
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
if (i > max) return false;
max = Math.max(max, i + nums[i]);
if (max >= nums.length - 1) return true;
}
return true;
}
}
6. 跳跃游戏II
用贪心按“覆盖区间”分层:在当前层内不断更新能到达的最远位置,走到层尽头时才 +1 跳,最终得到最少跳跃次数。
class Solution {
public int jump(int[] nums) {
int jumps = 0;
int curEnd = 0; int farthest = 0;
for (int i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i == curEnd) {
jumps++;
curEnd = farthest; }
}
return jumps;
}
}
7. K次取反后最大化的数组和
LT.1005. Maximize Sum Of Array After K Negations
目标:通过最多 k 次取反,让数组和最大
- 先按绝对值从大到小排序,大的数先处理
- 从左到右:如果是负数并且
k > 0 → 翻转它(收益最大)
- 如果最后 k 还剩
- 如果是偶数:反复翻转最后一个,不影响最终 sum , 相当于没有翻转
- 如果是奇数:也是反复翻转最后一个,等价于将最小的正数翻成负数
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
nums = IntStream.of(nums)
.boxed()
.sorted((a, b) -> Integer.compare(Math.abs(b), Math.abs(a)))
.mapToInt(Integer::intValue)
.toArray();
for (int i = 0; i < nums.length && k > 0; i++) {
if (nums[i] < 0) {
nums[i] *= -1;
k--;
}
}
if (k % 2 == 1) {
nums[nums.length - 1] *= -1;
}
int sum = 0;
for (int num : nums) {
sum += num;
}
return sum;
}
}
8. 加油站
LT.134. Gas Station
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
int curSum = 0; int totalSum = 0; int start = 0; for (int i = 0; i < n; i++) {
int diff = gas[i] - cost[i];
curSum += diff;
totalSum += diff;
if (curSum < 0) {
start = i + 1;
curSum = 0;
}
}
if (totalSum < 0) {
return -1;
}
return start;
}
}
9. 分发糖果
LT.135. Candy
- 从左往右: 保证「右边评分更高 ⇒ 右边糖更多」
- 从右往左: 保证「左边评分更高 ⇒ 左边糖更多」
- 最终对每个位置取两次规则中的最大值。因为一个孩子既可能需要满足左规则,也可能需要满足右规则
class Solution {
public int candy(int[] ratings) {
int n = ratings.length;
int[] candies = new int[n];
Arrays.fill(candies, 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 sum = 0;
for (int num : candies) sum += num;
return sum;
}
}
10. 柠檬水找零
LT.860. Lemonade Change
思路(贪心)
核心:只用 5 和 10 两种零钱来找零,并且尽量保留更多的 5 元。
维护两个计数:
- five:当前手里有多少张 5 元
- ten:当前手里有多少张 10 元
逐个处理 bills[i]:
-
收 5 元
-
收 10 元(需要找 5 元)
-
收 20 元(需要找 15 元) 贪心策略:
- 优先用 10 + 5(消耗一个 10,一张 5)
- 否则再考虑用 5 + 5 + 5
- 否则就无法找零:
return false;
为什么 20 元优先用 10 + 5 ?
因为 5 元是万能找零单位,比如下一个顾客付 10 元时一定需要 5 元。
如果我们现在用掉 3 张 5 元,后面可能就没办法给 10 元的顾客找零了。
所以:尽量保留小额的 5 元,这就是贪心的关键。
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0;
int ten = 0;
for (int bill : bills) {
if (bill == 5) {
five++;
} else if (bill == 10) {
ten++;
if (five > 0) {
five--;
} else {
return false;
}
} else if (bill == 20) {
if (ten > 0 && five > 0) {
ten--;
five--;
} else if (five >= 3) {
five -= 3;
} else {
return false;
}
}
}
return true;
}
}