开头小技巧(先记着):1. dp 分析顺序是 连续 的(注意!逆序 和 顺序)。
2.遇到只分析一个数组的应该 条件反射 想起 以数组 i 为下标 xxx 的最大值。
此题我们可以由繁入简,一开始想着用暴力解如何解决?
我们学过DP思想,可以知道 有三大特性 最优子结构 - 重复子问题 - 无后效性
从爬楼梯 背包问题 等 经典的dp问题中 都可以知道 F(N) = xxx;最左边的 F(N) 表明是 -- 最优子结构,而整个公式表明是 -- 无后效性,那么重复子问题在哪呢?就在我们分析的 暴力解 中。
进入本题!
我们围绕着上述的经典dp的 背包问题。开始这道题的分析:
由于给的每家的钱数都是正整数,则每次偷家的钱应该是:F(n) = Max( 之前的偷钱 (起始为0) + 当前偷的钱 );
表格表示暴力解(遍历每一种可能性):
选择任意个作为偷家起点这里我们选择第一个
i = | 1 | 2 | 3 | 4 | 5 | |
数组 = | 2 | 7 | 9 | 3 | 1 | max |
i=1 | 2 | 0 | 0 |
0
| 0 | 2 |
i=2 | 2 | 0+2=0(相邻偷不了) 7(当前的钱) | 0 | 0 | 0 | 2 |
i=3 | 2 | 7 | 9+2=11 9+7=9(相邻偷不了) | 0 | 0 | 11 |
i=4 | 2 | 7 | 11 | 3+7=10 3+11=3(不能偷相邻的11) | 0 | 11 |
i=5 | 2 | 7 | 11 | 10 | 1+11=12 1+2=3 1+7=8 | 12 |
单纯以第一个为起点后,12 是偷到最多的 答案自然而然是 12.(只针对以起点为2的,答案正确只是碰巧)
进入DP分析(利用 开头技巧1 分析):
作为小偷的你,限制 你为所欲为 的唯一条件是:“不能偷相邻的房屋”。这里就产生了 偷 与 不偷 的问题 对应 背包问题中的 放 与 不放。 偷产生一种可能,不偷产生另一种可能 也就分析出 产生了--> 重复子问题。
这里呼应 开头提供的技巧2,则可以得 每个数组下标的值 表示当前所偷钱的最大值。所以我们只需要F(N) 与之前的最大值相加就可以得到答案 --> 最优子结构。
由上述表格,每个相加可以都应该选择最大的值 ,依次叠加,只影响后面,不影响前面, 可以分析出 F(i) = F(i) + Max ( F(0..i - 1) );( 此公式相加后 并不会 对前面的值 F(0..i - 1) )造成任何影响;--> 可得 无后效性。
设 max1 是 i - 1前 最大的值的下标, max2 是i - 1 前 第二大的值的下标, F(i) 是: 下标为 i 的值;
由此可得状态转移 dp公式:1. F(i) = F(i) + F(max1)(max1 != i - 1);
2. F(i) = F(i) + F(max2)(max1 == i - 1);
class Solution {
//要明白每个下标的值表示:能偷到钱数的最大值。
public int rob(int[] nums) {
int n = nums.length;
if (n == 1) return nums[0];//只能抢一家
int max1 = nums[0] < nums[1] ? 1 : 0;
int max2 = max1 == 1 ? 0 : 1;
for (int i = 2; i < n; i++) {
nums[i] += nums[max1 != i - 1 ? max1 : max2]; //2.如果抢到金额最大值相邻(i - 1)的话,那就去抢之前的最大值。
if (nums[max1] < nums[i]) {
max2 = max1; //1.保存之前的最大值。
max1 = i;
}
}
return nums[max1];
}
}
ps:大二菜鸡一枚,第一次写博客,写得有错误 欢迎指正!
上面开头的两个小技巧 是 自己最近刷dp题 的感觉,就是那种只可意会不可言传,你懂吧?~