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