有时候,很难一眼看出一道题是贪心还是 DP。
为方便大家练习,我把比较套路的贪心题目放在前面,更灵活的思维题和构造题放在后面。每个小节的题目均按照从易到难的顺序排列。
如果做题时没有思路,推荐看看本文第五章的「思考清单」。
有两种基本贪心策略:
优先考虑最小/最大的数,从小到大/从大到小贪心。
如果答案与数组元素顺序无关,一般需要排序。排序后,可以遍历计算。
思维扩展:
同上,从最小/最大的元素开始贪心。
同上,从最小/最大的元素开始贪心。
对于无法排序的题目,尝试从左到右/从右到左贪心。思考第一个数/最后一个数的贪心策略,把 n 个数的原问题转换成 n−1 个数(或更少)的子问题。
读者可以对比下面的题目和 动态规划题单 中的线性 DP、状态机 DP 的区别,思考什么情况下只能 DP 不能贪心,从而加深对「局部最优」和「全局最优」的理解。
把数组/字符串划分成满足要求的若干段,最小化/最大化划分的段数。
思考方法同上,尝试从左到右/从右到左贪心。
读者可以对比下面的题目和 动态规划题单 中的划分型 DP 的区别,思考什么情况下只能 DP 不能贪心,从而加深对「局部最优」和「全局最优」的理解。
枚举题目的其中一个变量,将其视作已知条件,然后在此基础上贪心。
也可以枚举答案,检查是否可以满足要求。(类似二分答案)
交换论证法(exchange argument)用于证明一类贪心算法的正确性,也可以用来启发思考。做法如下:
也可以不用猜想,而是计算「先 ai 后 aj」和「先 aj 后 ai」对应的答案,通过比较两个答案谁更优,来确定按照何种顺序处理数据。
思维扩展:
相似题目:
给定正整数数组,每次操作,把数组中的两个数各减少一,并去掉变成 0 的数。目标:使最后剩下的数最小,或者最大化操作次数。
由于每次操作的都是两个下标不同的数,把这些下标按顺序拼接,可以构造出一个相邻元素不同的序列。例如 (1,2),(2,3),(3,4) 这三个操作,可以拼接成 [1,2,3,2,3,4]。
扩展:
一般要用到堆。
区间贪心有如下经典问题:
任务:总结上述四种区间贪心问题的解法,尤其是排序的规则和理由,什么时候要按照左端点排序?什么时候要按照右端点排序?排序的目的是什么?欢迎在评论区分享你的总结笔记。
变形:每个区间有各自的分数,从中选一些两两互不相交的区间,最大化得分之和。详见 动态规划题单 中的「§5.4 不相交区间」。
本质上和不相交区间是一样的。
可能算不上贪心,但为了题单的完整性,也放到这个分类中。
字典序的定义如下:
字典序的定义也可以推广到数组上,按照上述方法比较两个数组的字典序。
部分题目也出现在其他贪心分类中,为了题单的完整性整理到一起。
见 数据结构题单 中的「§3.4 合法括号字符串」。
给定两个长度均为 n 的数组 a 和 b,可以交换同一数组内的元素,最小化/最大化
a[0]⋅b[0]+a[1]⋅b[1]+⋯+a[n−1]⋅b[n−1]
把 a 和 b 都从小到大排序,可以最大化上式。
把 a 从小到大排序,b 从大到小排序,可以最小化上式。
思维扩展:
栅栏问题:长为 n 米的篱笆栅栏,围成一个矩形,矩形面积最大是多少?
变形:长为 n 米的栅栏分成 k 份,每份围成一个正方形,面积之和最小是多少?
给定数组,每次操作可以把其中一个数加一/减一,把所有数都变成一样的,最少要操作多少次?
把所有数都变成数组的中位数是最优的。
回想一下高考数学的最后一题,三个小问,前两小问让你计算一些特殊情况,第三小问让你计算/证明一个一般的结论。这其实就是从特殊到一般的思考方式,我们在做算法题(尤其是思维题和构造题)时,也可以从最简单、最特殊的情况开始,去探索题目的性质,逐渐过渡到一般情况。
思考清单
思考这些特殊情况,有时会产生求解原问题的灵感。
从一般到特殊。
构造题会给定一些约束,我们要构造一个满足这些约束的数组/字符串等。
思考方式和第五章的「思考清单」是一样的,在特殊情况中寻找灵感。
如果想做更多思维题和构造题,可以去 Codeforces 看看。
问:贪心和 DP 的区别是什么?
答:DP 可以视为带记忆化的暴力搜索,只要不遗漏任何分支,答案一定是对的。贪心可以视为带剪枝的搜索,如果贪心策略不对,就容易贪过头,把正确的分支给剪掉。
问:有没有万能方法,判断一道题是贪心还是 DP?
答:很难。如果不知道题目类型,把 DP 想成贪心的大有人在。我的建议是先思考 DP 能不能做,再思考贪心。如果 DP 的时间复杂度足以通过题目,就不用思考贪心策略了。
此外,这也说明按照题单刷题的缺点:提前知道题目类型,跳过了一些思考步骤。如何弥补这个缺点?请看 如何科学刷题。
欢迎关注 B站@灵茶山艾府
如果你发现有题目可以补充进来,欢迎评论反馈。