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.
这道题别看前面铺垫了一堆东西,其实就是让我们在一个数组中找到一些数,使得这些数之和最大,其中这些数两两不能相邻。那么这道题我们应该怎么来思考呢?
我们不妨可以这样想。假设数组长度为n,要想选出来的数字之和最大并且两两不能相邻,很明显选n/2个数至多(n/2+1)个数出来的和是最大的;比如[1000,80,700,5000,10000],当选出1000、700和10000这三个数时,结果是最大的。那么我们现在来考虑如何选数的问题。
我们都知道,每个数无非就两种情况,要么被选,要么没有被选。而实际上,这道题可以看成是求选了最后一个数之和与没有选最后一个数之和的最大值。所以我们可以尝试用两个数组select和no_select来分别存储选中当前元素的最大和与没有选中当前元素的最大和。
这样的话,初始化情况下:select[0]=nums[0],no_select[0]=0。在遍历数组的过程中,注意到要考虑选中的数两两不能相邻,所以有两种情况如下:
(1)如果不选当前元素nums[i],那么在这种情况下,上一个元素nums[i-1]可选可不选,取决于二者的最大值,即no_selcet[i]=max(select[i-1],no_select[i-1])。
例如:
a. [80,1000,700,5000,10000],当遍历到元素5000时,我们可以看到select[2]=80+700=780,no_select[2]=1000,很明显有no_select[3]=max(780,1000)=1000。此时第2个元素没有被选中时之和最大。
b. [1000,80,700,5000,10000],当遍历到元素5000时,我们可以看到select[2]=1000+700=1700,no_select[2]=1000,很明显有no_select[3]=max(1700,1000)=1700。此时第2个元素被选中时之和最大。
(2)如果选中当前元素nums[i],那么前面一个元素nums[i-1]肯定不选,因此有select[i]=nums[i]+no_select[i-1]。
最后我们只需得到两个数组最后一个元素的最大值即可,可以看到时间复杂度为O(n)。
class Solution {
public:
int rob(vector<int>& nums) {
int size = nums.size();
if (size == 0) {
return 0;
}
int *select = new int[size], *no_select = new int[size];
select[0] = nums[0];
no_select[0] = 0;
for (int i = 1; i < size; i++) {
no_select[i] = max(select[i - 1], no_select[i - 1]);
select[i] = nums[i] + no_select[i - 1];
}
return max(select[size - 1], no_select[size - 1]);
}
};
从数列A[0], A[1], A[2], …, A[N-1]中选若干个数,要求对于每个i(0 <= i < N-1),A[i]和A[i+1]至少选一个数,求能选出的最小和,其中1 <= N <= 100000, 1 <= A[i] <= 1000。
请为下面的Solution类实现解决上述问题的函数minSum,函数参数A是给出的数列,返回值为所求的最小和.
class Solution {
public:
int minSum(vector& A) {}
};例1:A = {2, 5, 2},答案为4.
例2:A = {2, 5, 4},答案为5.
这道题和上面那道题非常类似,只是把最大和问题改成了最小和问题,并且限制条件发生了一点小变化,要求我们在数组A中A[i]和A[i+1]中至少选中一个数。
借鉴上面那道题的思路,同样我们用两个数组a和b来分别存储没有选中当前元素i与选中当前元素i的最小和。
初始化情况下:a[0]=0,b[0]=A[0]。在遍历数组的过程中,注意到要考虑A[i]和A[i+1]中至少选中一个数,同样有两种情况如下:
(1)如果不选当前元素A[i],那么前面一个元素A[i-1]肯定被选中,因此有a[i]=b[i-1]。
(2)如果选中当前元素A[i],那么在这种情况下,上一个元素A[i-1]可选可不选,取决于二者的最小值,即b[i]=A[i-1]+min(a[i-1],b[i-1])。
例如:
a. [4,1,2,3],当遍历到元素2时,我们可以看到a[1]=4,b[1]=1,很明显有b[2]=2+min(4,1)=3。此时第1个元素被选中时之和最小。
b. [4,5,2,3],当遍历到元素2时,我们可以看到a[1]=4,b[1]=5,很明显有b[2]=2+min(4,5)=6。此时第1个元素没有被选中时之和最小。
最后我们只需得到两个数组最后一个元素的最小值即可,可以看到时间复杂度为O(n)。
这两道问题的关键在于分析出数组遍历的过程中,选中元素之和的动态变化,搞定了这点,类似的问题都没有什么太大的问题。
class Solution {
public:
int minSum(vector<int>& A) {
int size = A.size();
if (size == 1) {
return A[0];
}
int* a = new int[size];
int* b = new int[size];
a[0] = 0;
b[0] = A[0];
for (int i = 1; i < size; i++) {
a[i] = b[i - 1];
b[i] = A[i] + min(a[i - 1], b[i - 1]);
}
return min(a[size - 1], b[size - 1]);
}
};
以上是我对这两道问题的一些想法,有问题还请在评论区讨论留言~