Dynamic Programming学习笔记 (5) - 打家劫舍一 (力扣 #98)

235 阅读2分钟

DP问题主要以应用题形式出现,其字面形式可以千变万化,而解题的第一步就是要从题目的字里行间抽象出其内在的数学和逻辑含义。以下面的题目为例,

一条街上有若干个房子依次排列,每个房子里都有一定数量的现金,
一个小偷计划去这些房子偷钱,条件是不能偷两个相邻的房子。
给定一个nums数组,数组中的元素代表每个房子里的现金数量,
问这个小偷最多可以得到的现金总数。

实例如下:

nums = {5, 3, 4, 11, 2}

答案是小偷选择第1个和第4个房子,总和是16。

我们不难看出这个问题实际要解决的是从给定的数组中挑选若干互不相邻的元素,并使它们相加之和最大化。

解题思路如下:

对于数组的第一号元素,我们有两个选择,选或不选,如果选的话,我们就将第一号元素的值计入总和,然后就得跳过第二号元素,对第三号元素再做同样的选择;而如果不选第一号元素的话,那下一步就是对第二号元素做同样的选择。以此类推,对于数组中的各个元素,我们都有相同形式的选择,并且每一步的选择目的都是使总和最大化。

归纳为数学表达式, 就是

F(k) 
= 0 (k > N)
= max(nums[k] + F(k + 2),  F(k + 1))

其中k代表数组元素下标,N是数组长度

Java代码如下

class BottomUp {
    public int rob(int[] nums) {
        int N = nums.length;

        if (N == 1) {
            return nums[0];
        }

        int[] dp = new int[N + 1];
        dp[N - 1] = nums[N - 1];

        for (int i = N - 2; i >=0; i --) {
            int a = nums[i] + dp[i + 2];
            int b = dp[i + 1];
            dp[i] = Math.max(a, b);
        }

        return dp[0];
    }
}

上面的代码中使用了一个长度为N + 1的DP数组,与斐波那契数列问题类似,我们可以进行进一步的优化来减少空间复杂度。