首页 > 代码库 > LeetCode中 House Robber问题随笔

LeetCode中 House Robber问题随笔

先附上题目链接,有两问:

https://leetcode.com/problems/house-robber/

https://leetcode.com/problems/house-robber-ii/

第一问题目如下:

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.

大意就是去偷一排房子,每间房子中的钱数已知,偷的时候不能连续进入相邻的两间房屋,也就是说如果上一家偷了,下一家就不能偷,求出一个最优路径以便偷到最多的钱。

第二问问题就是加了一个限制,即所有房间成环状分布,也就是说首位不能同时偷。

因为没有规定房间数量,也就是说是一个动态的数组问题,可以简化为一个变种树状图:

技术分享

树状图自生长法:

第一个想法是让计算机重现这个树状图,即先写出1和0,然后1后面的值只能为0,0后面的值分为1和0两个:

我们可以创建一个2维数组,叫做result_map[][],行数代表所有可能的情况,列数代表总共的房间数,若一共有5间房,1 0 1 0 1代表隔间偷这种情况。

那么一共有多少种情况呢, 我们可以从树状图中看到,随着房间数从1到5的变化,总的可能情况为2 3 5 8 13,遵循斐波那契数列,即,对于n的房间数,总的情况为:

技术分享

是一个提前两项的通项公式,因此根据输入的房间总数,我们可以得到最后这个树状图矩阵的大小:result_map[fab(n)][n].

 

1.在第一列的第1,2行记录1,0,代表第一间房有偷与不偷两种选择。

技术分享

 

2.从第二列,若同一行上一列是1,则这一列为0;若上一列为0,这一列为1。

技术分享

 

3.判断出上一列为0的同时,将计数器(这里记为count0)加1,同时在count0+2行开始,记下这列为0的情况。

 技术分享

 4.复制当前行前几列的数据。

技术分享

特别说明的是这个算法是一列一列操作的,对于每一列来说,只需要遍历上一列中所有的可能性即可,对于第2列,遍历的行数是第一列总的可能情况(2种);而对于第4列,要遍历的行数是第3列总的可能情况(5种),这样循环计算可以保证前一列一定是有意义的值,减少初始化的值。

5.计算出整个矩阵后,将矩阵与提前设置的房间里钱数数组相乘,即可得出每种路径的总钱数。

这部分代码如下():

  1 // rob2.cpp : 定义控制台应用程序的入口点。  2 //  3   4 #include "stdafx.h"  5 #include <vector>  6 #include <iostream>  7 #include <iomanip>  8 #include <math.h>  9 using namespace std; 10  11 int fab(int number) 12 { 13     if (number <1) 14         return 1; 15     else 16         return (1 / sqrt(5))*(pow(((1 + sqrt(5)) / 2), number + 2) - pow(((1 - sqrt(5)) / 2), number + 2)); 17 } 18  19 int _tmain(int argc, _TCHAR* argv[]) 20 { 21  22     int i, j,k, c, r; 23     int countP=0; 24     int number; 25     cout << "enter number of houses: \n"; 26     cin >> number; 27  28     vector<int> money_arr(number); 29     cout << "money for each house: " << endl; 30     for (int arr = 0; arr < number; arr++) 31     { 32         money_arr[arr] = rand() %100; 33         cout << money_arr[arr] << " "; 34     } 35     cout << endl; 36  37     int total; 38     total = fab(number); //计算总的可能性 39     int row = 0; 40     row = total; 41     int column = number + 1; //给计算总钱数留了一列 42  43     vector<vector<int> > result_map(row, vector<int>(column)); 44  45      46  47     result_map[0][1] = 1; 48     result_map[1][1] = 0; 49  50  51  52  53     for (c = 2; c < column; c++) 54     { 55         for (r = 0; r < fab(c-1); r++)      //第c列的有效 56         { 57             if (result_map[r][c - 1] == 1) 58             { 59                 result_map[r][c] = 0;         60             } 61             else if (result_map[r][c - 1] == 0) 62             { 63                 result_map[r][c] = 1; 64                 countP++; 65                 result_map[countP+1][c] = 0; 66                     for (i = c - 1; i > 0; i--) 67                     { 68                         result_map[countP+1][i] = result_map[r][i]; 69                     }                 70             }  71             else{} 72         } 73          74     } 75  76  77  78     for (c = 1; c < column; c++) 79     { 80         for (r = 0; r < row; r++) 81         { 82             result_map[r][0] = result_map[r][0] + money_arr[c - 1] * result_map[r][c]; 83         } 84     } 85      86     cout << endl; 87     cout << "1为抢 " << "0为不抢。" << endl; 88     cout << endl; 89     cout << setw(6) << "总钱数" << setw(6) << "第一家" << setw(6) << "第二家" << setw(6) << "第三家"; 90     cout << setw(6) << "第四家" << setw(6) << "第五家" << setw(6) << "第六家" << "..." << endl; 91  92     for (j = 0; j < row; j++) 93     { 94         for (k = 0; k < column; k++) 95         { 96             cout << setw(6) << result_map[j][k] << right; 97         } 98         cout << endl; 99     }100 101 102 103     return 0;104 }

运行结果如图:

技术分享

只需要选出第一列最大值所对应的路径即可。

对于第二问来说,只要排除首尾都为一的矩阵即可。

 

基于斐波那契数列规律的应用:

上述方法存在一个3次的嵌套循环,对于算法来说应该避免,这个方法中我们可以寻找树状图中的规律,帮助程序画出所有的情况:

技术分享

 

 这个算法遵循的基本规律是:

1.对于总的房间数n,最终的分叉数为fab(n). —— fab(int n)方法在前面:  fab(0)=1,fab(1)=2,fab(2)=3,fab(3)=5,fab(4)=8,fab(5)=13...

2.在这之中若偷第一间房(第一列为1),会有fab(n-2)种可能性,第一间房不偷(为0),会有fab(n-1)种可能性。

3.每个1后面都是0,而对于第i间房每个0后面,有fab(n-i)个可能性,1后面只有fab(n-i-1)种。

举个例子:在总房间数为5的情况下,第3列的0后面有fab(5-3)=3种可能性,而1后面有fab(5-3-1)=2种可能性。

 

这个规律怎么用呢:

我们以n=4举例:

Step 1.最终有8种可能性,其中第一间抢,有3种,不抢,有5种,则第一列有3个1,5个0:

技术分享

Step 2:

进入第二列,一后面都是0:

技术分享

Step 3:而对于这5个0,其实都是第一步中那一个0的衍生点,根据规律3,这里面有2个1,3个0:

技术分享

Step 4:3个连续的0依然是上一步一个0的衍生点,这之中有一个1,2个0:

技术分享

Step 5:最后一列的0是成对出现的,只需要按顺序填一个1,一个0即可:

技术分享

这个方法代码如下,很杂,有些地方还需要改进,但是结果没有问题,效果一样:

 

  1 // Rob_project.cpp : 定义控制台应用程序的入口点。  2 //  3   4 #include "stdafx.h"  5 #include <vector>  6 #include <iostream>  7 #include <iomanip>  8 #include <math.h>  9 using namespace std; 10  11 int fab(int number) 12 { 13     if (number <1) 14         return 1; 15     else 16     return (1 / sqrt(5))*(pow(((1 + sqrt(5)) / 2), number + 2) - pow(((1 - sqrt(5)) / 2), number + 2)); 17 } 18  19 int _tmain(int argc, _TCHAR* argv[]) 20 { 21  22  23     int n; 24     cout << "enter number of houses: \n"; 25     cin >> n; 26  27     vector<int> money_arr(n); 28     cout << "money for each house: " << endl; 29     for (int arr = 0; arr < n; arr++) 30     { 31         money_arr[arr] = rand() % 100; 32         cout << money_arr[arr] << " "; 33     } 34     cout << endl; 35     int total; 36     total = fab(n); //计算总的可能性 37     int row = 0; 38     row = total; 39     int column = n + 1; 40  41     vector<vector<int>> result_map(row, vector<int>(column)); 42  43     for (int i = 0; i < total; i++) 44     { 45         if (i < fab(n - 2)) 46             result_map[i][1] = 1; 47         else 48             result_map[i][1] = 0; 49     } 50     int k,j = 0; 51  52     for (j = 2; j < column - 1; j++) 53         for (k = 0; k < row;) 54         { 55         if (result_map[k][j - 1] == 1) 56         { 57             result_map[k][j] = 0; 58             k++; 59         } 60         else if (result_map[k][j - 1] == 0) 61         { 62             int number_1 = fab(n - j - 1); //对于每个0后都有fab(n-j+1)的可能性,其中有fab(n - j - 1)个1,fab(n-j)个0 63             int number_0 = fab(n - j); 64             for (int i = 0; i < number_1; i++) 65             { 66                 result_map[k][j] = 1; 67                 k++; 68             } 69             for (int i = 0; i < number_0; i++) 70             { 71                 result_map[k][j] = 0; 72                 k++; 73             } 74         } 75         } 76     for (k = 0; k < row; k++)                //最后一列 77     { 78         if (result_map[k][column - 2] == 1) 79         { 80             result_map[k][column - 1] = 0; 81         }     82         else if (result_map[k][column - 2] == 0) 83         { 84             result_map[k][column - 1] = 1; 85             k++; 86             result_map[k][column - 1] = 0; 87         } 88              89     } 90      91      92                  93     for (int rr = 0; rr < row; rr ++) 94     { 95         for (int cc = 1; cc < column; cc ++) 96         { 97             result_map[rr][0] = result_map[rr][0] + result_map[rr][cc] * money_arr[cc - 1]; 98         } 99     }100 101 102     cout << endl;103     cout << "1为抢 " << "0为不抢。" << endl;104     cout << endl;105     cout << "总抢劫数" << setw(6) << "第一家" << setw(6) << "第二家" << setw(6) << "第三家" << setw(6) << "第四家" << setw(6) << "第五家" << setw(6) << "第六家" << setw(6)<<"..."<<endl;106     cout << "   ";107 108     for (int j = 0; j < row; j++)109     {110         for (int k = 0; k < column; k++)111         {112             cout  << result_map[j][k] <<setw(6)<<right;113         }114         cout << endl;115     }116     117 118 119     return 0;120 }

结果跟第一种完全一样:

技术分享

但是数组的排序方式不同,这一种更整齐一些。

 以上。

 

LeetCode中 House Robber问题随笔