分治、记忆化搜索、动态规划:家财万贯,劫你无患
2023-12-11 16:28:59
在动态规划的宝库中,198. 打家劫舍 算得上是入门必备的一道经典习题,它的解法由浅入深,将分治、记忆化搜索和动态规划三个重要思想巧妙结合,堪称算法之美。
故事的背景是这样的:有 N 家相邻的房屋排成一排,每家房屋中都藏有不同数额的财物。你是一位劫匪,计划对这 N 家房屋进行抢劫,但你必须遵守一个不成文的规定:相邻的房屋不能同时被抢劫,否则你就有被抓获的风险。
你的目标是,在不惊动任何人的情况下,抢劫这些房屋,并最大化你的收益。
分治:问题拆解,逐个击破
我们先从最简单的分治法入手。分治法的思想是将大问题分解成若干个小问题,分别求解这些小问题,然后再将它们的结果组合起来,从而得到大问题的解。
在打家劫舍问题中,我们可以将 N 家房屋划分为两组:第一组包含房屋 1 到 N-1,第二组包含房屋 2 到 N。那么,我们就可以先计算第一组房屋的最大收益,再计算第二组房屋的最大收益,最后将两个收益相加,即可得到整个问题的解。
这种分治法虽然简单,但它的时间复杂度却高达 O(2^N),因为对于每一组房屋,我们都需要计算所有可能的抢劫方案,这显然是一个指数级的时间复杂度。
记忆化搜索:避免重复计算,节约时间
为了降低分治法的时间复杂度,我们可以引入记忆化搜索的思想。记忆化搜索的原理是,对于每一个子问题,我们只计算一次,并将计算结果存储起来。当我们再次遇到同样的子问题时,就可以直接从存储器中取出计算结果,而无需重新计算。
这样一来,分治法的时间复杂度就可以从 O(2^N) 降低到 O(N^2),因为对于每一个子问题,我们只需要计算一次,而不用重复计算。
动态规划:状态转移,递推求解
记忆化搜索虽然可以降低分治法的时间复杂度,但它仍然不是最优解。因为记忆化搜索仍然需要为每一个子问题存储计算结果,这会消耗额外的空间。
为了进一步优化算法性能,我们可以引入动态规划的思想。动态规划的原理是,对于每一个子问题,我们只计算一次,并将计算结果存储起来。当我们再次遇到同样的子问题时,就可以直接从存储器中取出计算结果,而无需重新计算。
不同于记忆化搜索的是,动态规划通过使用状态转移方程来计算子问题的解,而无需将计算结果存储起来。这样一来,动态规划既可以避免重复计算,又可以节省空间,可谓一举两得。
状态压缩:空间优化,再上层楼
动态规划已经是一种非常高效的算法了,但是我们还可以更进一步,使用状态压缩来进一步优化算法性能。状态压缩的思想是,通过减少状态的数量来降低算法的空间复杂度。
在打家劫舍问题中,我们可以将状态定义为 (i, j),其中 i 表示当前房屋的索引,j 表示是否抢劫了上一家房屋。那么,我们就可以将状态空间从 O(N^2) 压缩到 O(2),因为对于每一个 i,我们只需要考虑是否抢劫了上一家房屋两种情况。
通过使用状态压缩,动态规划算法的空间复杂度就可以从 O(N^2) 降低到 O(2),大大提高了算法的性能。
结语
通过对 198. 打家劫舍 问题的逐步剖析,我们学习了分治、记忆化搜索、动态规划和状态压缩这四种重要的算法思想。这四种思想在解决动态规划问题时经常被用到,掌握了它们,你就可以轻松应对各种动态规划问题。