House Robber理解分析

上原题:
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之前的值都没有用了,覆盖掉就好。

头一次写这种东西,讲的比较晕,不足之处还请各位指点!!!

时间: 2024-12-13 06:26:36

House Robber理解分析的相关文章

JDK动态代理深入理解分析并手写简易JDK动态代理(上)

原文引用https://www.dazhuanlan.com/2019/08/26/5d6300df6f20f/ 博客真的是好几个月没更了,2019新年第一篇,继续深入动态代理,前两篇简单分析了动态代理的实现原理之后,这次继续深入了解具体的实现方式,并手写一套简易的动态代理已加强理解: 本博客关于Java动态代理相关内容直达链接: JDK动态代理浅析 Cglib动态代理浅析 JDK动态代理深入理解分析并手写简易JDK动态代理(上) JDK动态代理深入理解分析并手写简易JDK动态代理(下) 博客真

JS按位非(~)运算符与~~运算符的理解分析

按位非运算符,简单的理解就是改变运算数的符号并减去1,当然,这是只是简单的理解能转换成number类型的数据. 那么,对于typeof var!==”number”的类型来说,进行运算时,会尝试转化成32位整形数据,如果无法转换成整形数据,就转换为NaN: JS在位运算上用了更简便的一种方法来实现这中运算,那么它的实现原理大致上可以这样理解:  var testData=-2.9; var testResult=(typeof testData==="number"&&

从整理上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换

一.首先我们来看看进程控制块PCB也就是task_struct,(源码) 选出task_struct中几个关键的参数进行分析 struct task_struct {volatile long state; //进程状态 /* -1 unrunnable, 0 runnable, >0 stopped */ void *stack; //进程内核堆栈 atomic_t usage; unsigned int flags; //进程标识符 /* per process flags, defined

对char类型的理解以及对补码的理解分析

今天遇到这样一个小程序,觉得当中有些问题很容易让人忽略的! 这个程序代码如下: 程序的结果为: 我想很多像我一样的小白可能才开始是想不明白为什么最后的结果是255吧!首先,我们得知道 strlen()是计算字符串长度的函数,但为什么最后得到的字符串长度是255呢?定义的数组a中不是有1000个元素,并且for循环也是执行999次吗? 对于char来说,我们得知道其隐含的结束标记是\0,当编译器识别一个char类型的变量时,读取到\0,则标志着结束:对于这个程序,我们还得注意char的取值围:-1

对TabHost、TabWidget的理解分析

要用到tab组件,布局layout中必须有TabHost文件,它有一个id,比如 android:id="@+id/tabhost" 或者android:id="@android:id/tabhost" 在TabHost中一般必须有TabWidget,这个主要是用来处理tab的位置.属性等.一般还有FrameLayout组件,用于定义显示的在Tab下显示的组件. 例如: TabHost tabs = (TabHost) findViewById(R.id.tabho

JAVA面向对象思想理解分析

1.面向对象是面向过程而言.两者都是一种思想.面向过程:强调的是功能行为.(强调过程.动作)面向对象:将功能封装进对象,强调了具备了功能的对象.(强调对象.事物)面向对象是基于面向过程的.将复杂的事情变简单了.面向过程-->面向对象:执行者-->指挥者将过程.功能封装进对象里面.面向对象三大特征:封装.继承.多态.(找对象,建立对象,使用对象,维护对象的关系.)老者境界:(万物皆对象!!!)例:公司招聘程序员:为了提高效率,公司老板从面向过程到面向对象(执行者-->指挥者)例:去饭店吃饭

Android IntentService源码理解 及 HandlerThread构建消息循环机制分析

前言:前面写了Handler的源码理解,关于Handler在我们Android开发中是到处能见到的异步通信方式.那么,在Android原生里,有那些也有到了Handler机制的呢?有很多,比如我们今天所要理解分析的IntentService就使用到了Handler.接下来,我们来深入了解一下. HandlerThread: IntentService使用到了Handler+HandlerThread构建的带有消息循环的异步任务处理机制,我们先简单看一下HandlerThread是如何工作的吧.

(&#160;转)性能测试--地铁模型分析

地铁模型分析 和绝大部分人一样,小白每天都要乘坐地铁上下班,那么就拿地铁来分析,再次深刻理解下性能.早上乘坐地铁上班,最典型的就是北京地铁1.5.10.13号线等,人多得简直没法形容!为了方便理解分析,先做如下假设. 某地铁站进站只有3个刷卡机. 人少的情况下,每位乘客很快就可以刷卡进站,假设进站需要1s. 乘客耐心有限,如果等待超过30min,就会暴躁.唠叨,甚至选择放弃. 按照上述的假设,最初会出现如下的场景. 场景一:只有1名乘客进站时,这名乘客可以在1s的时间内完成进站,且只利用了一台刷

libuv源码分析前言

Libevent,libev,libuv三者的区别所在? libevent提供了全套解决方案(事件库,非阻塞IO库,http库,DNS客户端),然而libevent使用全局变量,导致非线程安全.它的watcher结构也过大,把I/O.计时器.信号句柄整合在一起.而且(作者认为)libevent的附加组件如http和dns库都实现不好,且有安全问题. libev因libevent而诞生,对libevent做了改进,避免使用全局变量,拆分watcher等.另外libev去掉了外部库(比如http和d