打家劫舍(力扣)——扩展输出由哪些所偷金额组成
条件:不可以连续的偷。
分析思路:
假设一个有n间房,第i间房放有Mi金额的钱。
步骤一:定义原问题和子问题
- 原问题:从全部 n 间房中能够偷盗的最大金额;
- 子问题:从前 k 个(k<=n) 间房中能够偷的最大金额;
步骤二:定义状态
- 我们记 f(k) 为小偷能从前k间房中偷到的最大金额,这里k就表示这个小偷在子问题时的一个状态,即小偷仅能偷前 n 间房
步骤三:寻找状态转移方程
就是 寻找子问题之间的一个递推关系,
情况1:
- k=1时,只有第1间房可以偷,没得选。
情况2:
- k=2时,只有第一间和第二间房,可以偷,因为不能连偷,所有选金额较大的房偷。
情况3:
- k>=3时,分两种情况讨论:
(1)小偷已经偷了第k-1间房,这时小偷不能再偷第k间房,所有此时最多获取f(k-1)的钱财。
(2)小偷没有偷第 k-1 间房,根据定义 小偷在前 k-2 间房 能偷到的最打钱财为 f(k-2),又因为没有偷第 k-1 间房,所有小偷一定会去偷第 k 间房
因此,小偷出去两次情况下较大的金额去偷,即 f()=max{ f(k-1),f(k-2)+Mk}
java代码实现:
public int thief_DP(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int len = arr.length;
if(len==1) return arr[0];
//创建DP数组,来存储 前面所偷的金额
int[] dp = new int[len];
//初始条件
dp[0] = arr[0];
dp[1] = Math.max(arr[0], arr[1]);
for(int i=2;i<len;i++) {
dp[i]=Math.max(dp[i-1], dp[i-2]+arr[i]);
}
return dp[dp.length-1];
}
空间上做出优化
实际上只用到了前两位的值,然后更新数据
public int thief_DP1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int len = arr.length;
int cur = 0;
if(len==1) return arr[0];
//创建DP数组,来存储 前面偷的金额
// int[] dp = new int[len];
int pre2 = arr[0];
int pre1 = Math.max(arr[0], arr[1]);
for(int i=2;i<len;i++) {
cur = Math.max(pre1, pre2+arr[i]);
pre2 = pre1;
pre1 = cur;
}
return cur;
}
拓展——输出 偷窃的房屋编号**
要求返回 编号和金额(编号那里来?)
编号 可以通过 DP数组计算出来
例如,假设每间房屋的金额M={1,4,1,2,5,6,3},根据上面的代码得到,DP数组F=[1,4,4,6,9,12,12],F中第k个元素表示的含义是小偷能从前k间房中偷到的最大金额,例如 k=4,表示从前4间房(房间从1开始,自行转化为下标0)中偷到的最大金额是6(偷第2间和第4),最终计算求得 f = 12,表示从所有房间偷到最大金额为12,IND=[2,4,6],表示偷的房间编号。
IND=[2,4,6]如何来?
先知道两个结论:(1)对任意的 i 均有F(i)> M(i),当且仅当小偷只偷一间房子时 = 才成立;(2)F递增(不减,不要求严格)。
第1步:找到F中第一次出现的最大值的位置(在DP数组最后),此时是小偷偷的最后一间房。
比如:例子中 F最大值为12,所有第一次出现的对应下标为6,表示6号房是小偷偷的最后一个房,因此我们把6添加到IND数组。
第2步:记住第一步找到的下标6为ind,判断F(ind)于M(ind)的大小,根据结论1,F(ind)>M(ind),所有可以分两种情况:(1)若 F(ind)= M(ind),意味着小偷之前没有偷其他房
(2)若 F(ind)>M(ind)说明小偷还偷了其他房,如例子中:ind=6,F(ind)=12,M(ind)=6,那么我们可以用F(ind)减去 M(ind),得到结果就是在ind=6房间之前能偷到的最大金额,F(ind)-M(ind)=6;,然后我们找F里面第一个出现6的位置(倒着找),这个就是小偷偷的倒数第二间房屋,F(4)=6,所有把4号也添加到IND数组中,这样不断循环下去,直到F(ind)等于M(ind)为止。
代码实现
//给出所求的dp数组和原始数组arr,输出小偷所偷的金额
public void show(int[] dp,int[] arr) {
int ind[] = new int[dp.length];
int index=0;
//第一步
int i=dp.length-1; //指向偷了的
ind[0] = arr[i];
int val=0;
//第二部 判断
while(dp[i]>arr[i]) {
val = dp[i]-arr[i];
//找出对应dp下标
for(int j=0;j<dp.length;j++) {
if(val==dp[j]) {
i=j;
ind[++index] = arr[i];
break;
}
}
}
for(int k=index;k>=0;k--) {
System.out.print(ind[k]+" ");
}
}
完成啦~