打家劫舍(力扣)

打家劫舍(力扣)——扩展输出由哪些所偷金额组成

打家劫舍(力扣)_第1张图片
条件:不可以连续的偷。

分析思路:

假设一个有n间房,第i间房放有Mi金额的钱。
步骤一:定义原问题和子问题

  • 原问题:从全部 n 间房中能够偷盗的最大金额;
  • 子问题:从前 k 个(k<=n) 间房中能够偷的最大金额;

步骤二:定义状态

  • 我们记 f(k) 为小偷能从前k间房中偷到的最大金额,这里k就表示这个小偷在子问题时的一个状态,即小偷仅能偷前 n 间房

步骤三:寻找状态转移方程

就是 寻找子问题之间的一个递推关系
打家劫舍(力扣)_第2张图片
情况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]+" ");
		}
	}

完成啦~

你可能感兴趣的:(动态规划,力扣,算法,学习)