1.题目大意
假如你是一名小偷,现在准备去偷一条街道上的所有房子里的钱。为了防止偷盗,房子间有防盗装置:只要你偷盗了邻近的两间房子就会惊动警察。请问,如何偷盗才有最大收获?
输入代表房子钱财的数组,输出最大收获,如输入[1, 2, 3, 1]就应该输出4。
2.解题思路
以动态规划的思路来解决这个问题:可以将如何从[n1, n2, …, nk]这里面选择互不相邻的元素有最大的和的问题分为小问题,即在选了nk的情况下与不选nk的情况下选取最优值。如果C(i)表示从[1, i]区间的下标中选择互不相邻的元素的最大和,那么C(i) = max(C(i - 2) + array(i), C(i - 1)),即将从[1, i]中挑选元素的问题变为从选了i元素并从[1, i - 2]区间中继续挑选,还是不选i元素而从[i, i -1]区间挑选。
class Solution {
public:
int rob(vector<int>& nums) {
return sub(nums, nums.size() - 1);
}
int sub(vector<int>& nums, int index) {
return index < 0 ? 0 : max(sub(nums, index - 2) + nums.at(index), sub(nums, index - 1));
}
};
上面的这个方法虽然可行,但是如果a表示长度为i的数组的递归次数,b表示长度为i - 1的数组的递归次数,那么长度为i + 1的数组的递归次数就等于1 + a + b,即O(n) = O(n!)。里面有太多的重复计算和递归了,这时应该用memo数组将那些已经计算过了的值存储起来。注意,memo数组里面下标为i的元素就表示nums数组中[0, i]区间的元素中挑选的元素的和的最大值。
class Solution {
private:
int* memo;
public:
int rob(vector<int>& nums) {
int size = nums.size();
memo = new int[size];
for (int i = 0; i < size; i = i + 1)
memo[i] = -1;
int result = rob(nums, size - 1);
delete [] memo;
return result;
}
int rob(vector<int>& nums, int index) {
if (index < 0)
return 0;
if (memo[index] >= 0)
return memo[index];
memo[index] = max(rob(nums, index - 1), rob(nums, index - 2) + nums.at(index));
return memo[index];
}
};
但是,递归计算其实还是太消耗性能了,可以考虑用for循环来取代递归,尤其是当memo中i - 1下标的元素总比i下标的元素早计算出来。
class Solution {
public:
int rob(vector<int>& nums) {
int size = nums.size();
if (size == 0)
return 0;
int memo[size + 1];
memo[0] = 0;
memo[1] = nums.at(0);
for (int i = 1; i < size; i = i + 1) {
memo[i + 1] = max(memo[i], memo[i - 1] + nums.at(i));
}
return memo[size];
}
};
还能不能优化呢,其实还是可以的,只不过这会不是时间复杂度了,而是空间复杂度。从上面的代码可以看出,其实一直是用memo[i]以及memo[i - 1]元素的值来求导memo[i + 1],前面的比i - 1小的数没有再使用过,所以只要用两个数来取代数组就可以了。
所以,最终版代码如下:
class Solution {
public:
int rob(vector<int>& nums) {
int size = nums.size();
if (size == 0)
return 0;
int num1 = 0;
int num2 = 0;
for (int i = 0; i < size; i = i + 1) {
int temp = num1;
num1 = max(nums.at(i) + num2, num1);
num2 = temp;
}
return num1;
}
};