返回

LeetCode 198: 打家劫舍 - 最优解法

前端

LeetCode 198:打家劫舍

在寻求财富的激动人心中,窃贼策划着一次大胆的冒险,目标是一个排列成一行的神秘房屋,据传这些房屋里藏着令人垂涎的宝藏。然而,有一个障碍:一个警觉的守卫,严密监视着他们的举动。守卫规定,小偷不能连续偷窃相邻的房屋。

动态规划的艺术

为了最大限度地增加他们的战利品,小偷们求助于动态规划的精妙艺术。动态规划是一种巧妙的策略,它将复杂问题分解成更小的子问题,然后系统地解决这些子问题。在这个场景中,子问题是如何确定偷窃房屋 i 和不偷窃房屋 i 所获得的最大收益。

规划的步骤

  1. 初始化: 我们从一个空白画布开始,将收益数组 dp 初始化为零,代表偷窃房屋 0 和 1 时为零收益。
  2. 递归: 对于每个房屋 i,我们计算偷窃房屋 i(dp[i-1] + nums[i])和不偷窃房屋 i(dp[i-2])的收益。我们取这两个收益中的最大值作为 dp[i]。
  3. 优化: 我们不断更新 dp 数组,在前进时积累知识。通过这种渐进式的方法,我们避免了重复计算,提高了算法的效率。

Java 代码示例

public class HouseRobber {

    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }

        // 初始化收益数组
        int[] dp = new int[nums.length + 1];
        dp[0] = 0;
        dp[1] = nums[0];

        // 计算每间房屋的最佳收益
        for (int i = 2; i <= nums.length; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }

        // 返回最大收益
        return dp[nums.length];
    }
}

复杂度分析

  • 时间复杂度: O(n),其中 n 是房屋的数量。我们遍历房屋数组一次,对于每个房屋执行常数时间计算。
  • 空间复杂度: O(n)。我们使用了一个大小为 n+1 的 dp 数组来存储子问题的最优解。

示例

假设小偷们潜入了一排包含以下金额的房屋:

[1, 2, 3, 1]

使用动态规划算法,我们可以计算出小偷们能获得的最大收益:

dp[0] = 0
dp[1] = 1
dp[2] = max(dp[1], dp[0] + nums[1]) = max(1, 0 + 2) = 2
dp[3] = max(dp[2], dp[1] + nums[2]) = max(2, 1 + 3) = 4
dp[4] = max(dp[3], dp[2] + nums[3]) = max(4, 2 + 1) = 4

因此,小偷们能获得的最大收益是 4

结论

动态规划算法为解决 LeetCode 198 问题提供了一种优雅高效的方法。它通过分解问题并逐步构建最优解,巧妙地处理了复杂的决策过程。使用这种方法,小偷们能够制定出完美的计划,最大限度地增加他们的财富,同时避免守卫的警惕。

常见问题解答

  1. 什么是动态规划?

动态规划是一种解决复杂问题的方法,通过将问题分解成更小的子问题,然后逐步解决这些子问题来找到最优解。

  1. 为什么使用动态规划来解决 LeetCode 198 问题?

LeetCode 198 问题涉及到一个复杂的决策过程,动态规划允许我们系统地考虑所有可能的决策,从而找到最优解。

  1. 动态规划算法的复杂度是多少?

对于 LeetCode 198 问题,动态规划算法的时间复杂度为 O(n),空间复杂度为 O(n),其中 n 是房屋的数量。

  1. 如何使用代码实现动态规划算法?

Java 代码示例展示了如何使用动态规划算法解决 LeetCode 198 问题。它初始化一个收益数组,并使用递推关系计算每间房屋的最佳收益。

  1. 除了 LeetCode 198 之外,动态规划算法还有什么其他应用?

动态规划算法广泛应用于许多优化问题,例如最长公共子序列、最短路径和背包问题。