贪心算法

贪心的关键不是“每次选最大的”,而是先证明“当前这一步这样选,绝不会吃亏”,也就是局部决策不会破坏全局最优。

核心思路

贪心题最常见的切入角度就三个:

  1. 排序后,优先处理某种“更紧”的对象。
  2. 扫描时,始终维护当前最优边界。
  3. 构造答案时,每一步都做一个不会后悔的选择。

如果你没法说明“为什么不会后悔”,那这题大概率就不是纯贪心,而应该去想 动态规划 或搜索。

高频题型

区间类

这类题通常先排序,再维护“当前已选区间的结束位置”或“当前能覆盖到的最远位置”。

排序分配类

本质上是在问:把资源按什么顺序处理,损失最小、收益最大。

数字与字符串构造类

这类题经常和单调栈、堆、自定义排序混在一起考。

两个最有代表性的 Go 模板

模板一:区间调度

import "sort"
 
func eraseOverlapIntervals(intervals [][]int) int {
	if len(intervals) == 0 {
		return 0
	}
 
	sort.Slice(intervals, func(i, j int) bool {
		return intervals[i][1] < intervals[j][1]
	})
 
	count := 1
	end := intervals[0][1]
 
	for i := 1; i < len(intervals); i++ {
		if intervals[i][0] >= end {
			count++
			end = intervals[i][1]
		}
	}
 
	return len(intervals) - count
}

这里贪心的本质是:结束得越早,越给后面的区间留空间。

模板二:跳跃游戏 II

func jump(nums []int) int {
	step := 0
	end := 0
	farthest := 0
 
	for i := 0; i < len(nums)-1; i++ {
		if i+nums[i] > farthest {
			farthest = i + nums[i]
		}
		if i == end {
			step++
			end = farthest
		}
	}
 
	return step
}

这个模板不是在真的“跳”,而是在按层统计当前一步最远能扩到哪里,所以它和 BFS 分层思想 很像。

如何判断一道题能不能贪心

一个很实用的判断方法:先写出你直觉里的局部策略,再主动找反例。

如果下面三件事同时成立,贪心通常才比较稳:

  1. 局部最优决策有明确标准,比如最早结束、最远覆盖、最小代价。
  2. 当前选择不会影响已经做完的决策。
  3. 当前选择只会让后续空间更大,不会更小。

常见易错点

贪心最危险的地方不是实现,而是“你以为能贪,实际上不能贪”。

  • 0/1 背包不能靠简单贪心解决。
  • 多维约束题经常不是排一个字段就完事,排序规则需要严格推导。
  • 区间题经常会混淆“按起点排”还是“按终点排”,这个要看你维护的量是什么。
  • 股票问题里有些能贪,有些必须上状态机 DP,不能一概而论。

刷题顺序

  1. 先刷区间调度、区间覆盖,建立“排序 + 扫描”的感觉。
  2. 再刷跳跃游戏、分发饼干、分糖果,熟悉边界维护和资源分配。
  3. 最后再做去重字母、移掉 K 位数字、重构字符串这类构造题。

相关主题


返回:算法学习导航