上原题:
You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
故事翻译先不讲了,大致意思如下:
一个非负数组int[] a,挑选一系列元素,要求如下:
1、挑出的元素中,没有下标相邻的。
2、元素总和最大。
第一眼看见这道题,出色的英语能力让我瞬间明白了题目要求,但是可怜的逻辑分异能力让我一下就蒙了。一时间甚至找不出什么样的结果才是满足这两个要求的,完全没有思路。那么先镇定一下,想想正确结果应该是什么样子,首先,不能简单的隔一个元素取一个。比如如下的数组a=[100,1,1,100],a[0]a[2]与a[1]a[3]显然都不是最大的值,明显应该是a[0]a[3]。其次,正确结果中两个元素最多相差两个单位的位置,不可能差3个以上(如果查了三个,比如a[1]a[5],那么为什么不把a[3]也带上)。
带着这两个情况分析,还是找不出结果。于是我放弃了,百度了一下答案如下(转自:http://blog.csdn.net/xudli/article/details/44795737)
维持两个数组, 一个是包含最后一个字符的最大值, 一个是不包含最后一个字符的最大值. 更新两个数组, 最后求 max(d[n-1], b[n-1])即可.
public class Solution {
public int rob(int[] num) {
// 31 23 9 13 49 1 0
// 0 0 0 0
if(num==null || num.length==0) return 0;
int n = num.length;
int [] b = new int[n]; //include last element;
int [] d = new int[n]; //exclude last element;
b[0] = num[0];
d[0] = 0;
for(int i=1; i<n; i++) {
b[i] = d[i-1] + num[i];
d[i] = Math.max(b[i-1], d[i-1]);
}
return Math.max(d[n-1], b[n-1]);
}
}
这下看到答案了,但是还是不能理解这个b和d数组存的到底是什么?还有注释里写的包含最后一个字符的最大值都是什么意思。
仔细的研究了一下,具体思路如下:
假设mun={A,B,C,D,E,F,G}
那么当i=0时
b[0]=num[0]=A
d[0]=0
此时对比一下三个数组(i=0)
表1-1
0 | 1 | 2 | 3 | 4 | 5 | 6 | |
num | A | B | C | D | E | F | G |
b | A | ||||||
d | 0 |
i=1时 b[1]=d[0]+num[1]=0+B=B, d[1]=Math.max(d[0],b[0])=Math.max(0,A)
i=2时 b[2]=d[1]+num[2]=max(0,A)+C, d[2]=Math.max(b[1]+d[1])=Math.max(B+Math.max(0,A))
表1-2
0 | 1 | 2 | 3 | 4 | 5 | 6 | |
num | A | B | C | D | E | F | G |
b | A | B | |||||
d | 0 | max(0,A) |
表格就列到这里,下作如下解释:
代码中主要的几行分别是i=0时的
b[0] = num[0];
d[0] = 0;
与i>=1时的
b[i] = d[i-1] + num[i];
d[i] = Math.max(b[i-1], d[i-1]);
基本可以这么理解,这种答案,答题者把答案分为两类,一类是选最后一个,一类是不选最后一个,那么当数组长度为i的时候,元素总和的值为b[i-1]或d[i-1],哪个大,答案就是哪个。
那么这里有两点问题
1、凭什么b[i-1]或d[i-1]是最大值
2、为什么要分为选最后一个和不选最后一个这两类,为什么不分为选第一个或者不选第一个,另外,选最后一个和不选最后一个真的可以覆盖需要的所有情况吗?
带着上面两个问题我们解读一下之前的列表1-1与表1-2
在解读之前还需要一点理论支持,就是数学归纳法,解释如下:
如果当n=k时表达式f(k)成立,且能推出f(K+1)时成立,那么当n>=k时f(n)钧成立
好了现在我们来套用一下:
如果i=0时能,已知关于num[]的正确答案是Math.max(b[0], d[0]),那么如果能推出i=1时关于num[]的正确答案是Math.max(b[1], d[1]),即当i=num.length-1时能推出答案应该是Math.max(b[i], d[i]) 。
这其中有一点绕啊
因为目测我们列表中无论i=0和i=1时都是成立的,难道仅此就往后的答案都正确吗?
当然不会这么草草结束,因为我们要的不是i=0和i=1的结果,而是i=0与我们的构造过程(或者说推导过程)能推出i=1是对的,即i=1的结果不能靠目测。
解释如下:
首先i=0 b[0]=A,d[0]=0。结果应该是max(b[0],d[0])成立,也就是说,如果A不为0,那么应该选取数字的总和应该是A
好,这个显然是成立的,那么我们忘记i=0吧。假设i=k所选数字总和是max(b[k],d[k])。这个时候关键点来了:注意答题者对b与d的定义。b是当选取最后一个元素时,所选结果的和的最大值,d为不选最后一个元素时,所选结果的和的最大值。那么当i由k变为k+1时,都变化了哪些呢?
1、b[k]为当元素为k个时,选取最后一个元素时,所选结果和的最大值
2、d[k]为当元素为k个时,不选最后一个元素时,所选结果和的最大值
3、集合里的元素多了一个,以前是k个,现在是k+1个
有了上述三个条件,我们的b[k+1]和d[k+1]应该是多少呢?
先说d[k+1]:
由于d为不选最后一个元素,那么有没有最后一个元素对之前的结果没有影响,那么之前的最大值是什么呢,就是b[k]和d[k]中最大的那个(毕竟来了一个也不能选,之前哪个大就是哪个了)
即d[k+1]=max(b[k],d[k])
我们再来说b[k+1]:
b的定义为,选了最后一个元素的最大值的和那么b[k+1]一定要带上num[k+1]了,然后的问题是,之前的结果要用哪个?b[k]还是d[k],答案当然是d[k],因为根据b的定义b[k]里有num[k]啊,那如果再选了num[k+1],不是出现了num[k]和num[k+1]的情况了这就连续了,与要求不符。这也是为什么一定要构造出一个不选最后一个元素的最大值记录(即d),就是为了新来的元素准备的。
总结一下:b[k+1]应该是num[K+1]+d[k]
好了,看看我们的结果
d[k+1]=max(b[k],d[k])
b[k+1]=num[K+1]+d[k]
在对比一下代码中的公式
b[i] = d[i-1] + num[i];
d[i] = Math.max(b[i-1], d[i-1]);
看!!是不是如出一辙。
这里还需要说的一点是,包括我在内,有很多人也会觉得,不是b[k]就选d[k]吗?万一d[k]也不对呢?
带着这点疑问,我们往前追溯一下,d[k]的意思是,不选num[k]的和的最大值,那么如果我们想选num[k+1],就不能选num[k],并且还得选当k从0~k-1时,总和最大的值,那这个值不就是d[k]吗?
至此,证明流程结束!!
这份代码的控件复杂度是O(N),在网上还看到空间复杂度O(1)的,其思想跟这个代码是一样的,只不过这里用两个长度为n的数据记录包含最后一个字符的最大和是不包含最后一个字符的最大值,而他用了两个值记录,确实足够了,试想当计算n=k时,n=k-1之前的值都没有用了,覆盖掉就好。
头一次写这种东西,讲的比较晕,不足之处还请各位指点!!!