打家劫舍

如果我们是专业的小偷,并计划偷窃沿街房屋内部藏有的现金。影响我们偷窃的唯一因素就是相邻房屋装有互相连通的防盗系统,如果两间相邻的房屋在同一个晚上被小偷闯入,就会触发自动报警系统。

给定一个存储每个房间藏有现金金额的非负整数数组,计算我们在不触动报警系统的情况下,一夜之间能够偷窃到的最高金额。

这题很明显是一道动态规划的题目。为什么呢?因为两间屋子相邻,很明显就是说在这个数组中,我不能把相邻的两个元素取出来让他们最后加起来的总和最大。那么我就需要考虑,在某一个特定的位置,我取这个数和不取这个数,两种情况下对应的最优解。这样一来,我们很容易想到要用递归来做。递归的话,那么就是,我们在数组某个位置取最优解,那么这个地方的最优解,一定取决于它上一个位置和上上个位置的最优解的情况。而上一个位置和上上个位置的最优解的情况则需要确定上上个位置和上上上个位置的情况,所以,这样,我们需要给定最开始的两个值。

如果数组只有一个元素,那很明显最优解就是它;如果没有,那就直接返回0。

接下来,我们就可以用递归了。由于不能取用相邻的两个元素,那我们就考虑:取当前位置元素的最优解和不取当前位置元素的最优解。

下面上代码:

 1 private static int getBest(int[] data, int index) {  //用递归的方式
 2         if(data == null || index < 0){
 3             return 0;
 4         }
 5         if(index == 0){
 6             return 1;
 7         }
 8         data[index] = Math.max(getBest(data, index-1),getBest(data, index-2)+data[index]);
 9         return data[index];
10     }

这样就可以了。

但是如果我们自己分析就会发现这样写其实还是存在问题的,什么问题呢?我计算index-1时,是不是需要计算index-2和index-3?那我计算index-2时是不是要计算index-3和index-4?很显然这里重复计算了,而计算一次其实并不容易。这样很不划算。因此我们可以考虑换一种方式:

我们可以把数组对应位置的最优解存在一个数组中,通过循环不断取得最优解:

 1 private static int maxMoney(int[] data){  //用迭代的方式
 2         int len = data.length;
 3         int[] dp = new int[len];  //这个数组里面存最优解
 4         if(len == 0 || data == null){
 5             return 0;
 6         }else if(len == 1){
 7             return data[0];
 8         }
 9         dp[0] = data[0];
10         dp[1] = Math.max(data[0], data[1]);
11         for(int i = 2; i < len; ++i) {
12             dp[i] = Math.max(dp[i-1], dp[i-2]+data[i]);
13         }
14         return dp[len-1];
15     }

这样一来,解决了重复计算的问题。在任何一个位置想要达到最优,那么这个位置之前的位置,换种说法就是其子结构,也一定要有最优解。

然鹅呢,这样其实仍然可以优化。为啥呢,我们dp数组中的元素到底都只是实现了一个暂存的功能,既然是暂存,那我们还有必要专门开一个数组吗?没有。因此,我们可以设两个变量实现暂存的功能,从而降低算法的空间复杂度:

 1 private static int betterMaxMoney(int[] data) {
 2         int len = data.length;
 3         if(data == null || len == 0) {
 4             return 0;
 5         }else if(len == 1){
 6             return data[0];
 7         }
 8         int first = data[0];
 9         int second = Math.max(data[0], data[1]);
10         for(int i = 2; i < len; ++i) {
11             int temp = second;
12             second = Math.max(second, first+data[i]);
13             first = temp;
14         }
15         return second;
16     }

好,现在我们把这个问题稍微改复杂一下(其实也没复杂多少):我们现在偷窃的这个村庄的房屋是沿直线排布的,那么现在经过上一次的偷窃之后呢,村庄里的人加强了防范意识,把屋子改成圆形排布了,也就是说,第一个房子和最后一个房子之间也是相邻的了,我们现在又来偷东西了,那么这种情况下,怎样才能使得偷得的金额数最大呢?

其实不难,只不过,在传递参数时,我们不能从第一个屋子开始了,我们设置一个start屋子和一个end屋子,给定start和start+1的最优解,从start+2开始,一直到end设置最优解就可以了:

 1 private static int moreMoney(int[] data, int start, int end) {
 2         int length = data.length;
 3         if(data == null || length == 0){
 4             return 0;
 5         }else if(length == 1){
 6             return 1;
 7         }
 8         int first = data[start];
 9         int second = Math.max(data[start], data[start+1]);
10         for(int i = start+2; i <= end; ++i) {
11             int temp = second;
12             second = Math.max(data[i]+first, second);
13             first = temp;
14         }
15         return second;
16     }

来看看测试结果:

 

 最后来一波完整代码:

 1 package com.hw.list0710;
 2 
 3 public class Rob {
 4     private static int getBest(int[] data, int index) {  //用递归的方式
 5         if(data == null || index < 0){
 6             return 0;
 7         }
 8         if(index == 0){
 9             return 1;
10         }
11         data[index] = Math.max(getBest(data, index-1),getBest(data, index-2)+data[index]);
12         return data[index];
13     }
14 
15     //拥有最优子结构
16     private static int maxMoney(int[] data){  //用迭代的方式
17         int len = data.length;
18         int[] dp = new int[len];  //这个数组里面存最优解
19         if(len == 0 || data == null){
20             return 0;
21         }else if(len == 1){
22             return data[0];
23         }
24         dp[0] = data[0];
25         dp[1] = Math.max(data[0], data[1]);
26         for(int i = 2; i < len; ++i) {
27             dp[i] = Math.max(dp[i-1], dp[i-2]+data[i]);
28         }
29         return dp[len-1];
30     }
31 
32     //可以降低算法的空间复杂度
33     private static int betterMaxMoney(int[] data) {
34         int len = data.length;
35         if(data == null || len == 0) {
36             return 0;
37         }else if(len == 1){
38             return data[0];
39         }
40         int first = data[0];
41         int second = Math.max(data[0], data[1]);
42         for(int i = 2; i < len; ++i) {
43             int temp = second;
44             second = Math.max(second, first+data[i]);
45             first = temp;
46         }
47         return second;
48     }
49 
50     //房屋之间圆形分布
51     private static int moreMoney(int[] data, int start, int end) {
52         int length = data.length;
53         if(data == null || length == 0){
54             return 0;
55         }else if(length == 1){
56             return 1;
57         }
58         int first = data[start];
59         int second = Math.max(data[start], data[start+1]);
60         for(int i = start+2; i <= end; ++i) {
61             int temp = second;
62             second = Math.max(data[i]+first, second);
63             first = temp;
64         }
65         return second;
66     }
67 
68     public static void main(String[] args) {
69         int[] val = new int[]{2,7,9,3,1};
70         System.out.println(Math.max(moreMoney(val, 0, val.length-2), moreMoney(val, 1, val.length-1)));
71         int res3 = betterMaxMoney(val);
72         System.out.println(res3);
73         int res2 = maxMoney(val);
74         System.out.println(res2);
75         int res1 = getBest(val, val.length-1);
76         System.out.println(res1);
77     }
78 }

 

posted @ 2021-07-23 22:58  EvanTheBoy  阅读(74)  评论(0)    收藏  举报