目录
1 大盗阿福
按照之前集合的思路:
集合
f [ i ] f[i] f[i]:表示抢劫前 i i i 家店铺所获得的的收益
集合划分
- 左边:抢劫第 i i i 家店铺, f [ i − 2 ] + w [ i ] f[i-2]+w[i] f[i−2]+w[i](因为选择了第 i i i 家,第 i − 1 i-1 i−1 家就不能选择)
- 右边:不抢劫第 i i i 家店铺, f [ i − 1 ] f[i-1] f[i−1]
状态计算
f [ i ] = m a x ( f [ i − 2 ] + w [ i ] , f [ i − 1 ] ) f[i] = max(f[i - 2] + w[i], f[i - 1]) f[i]=max(f[i−2]+w[i],f[i−1])
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int f[N];
int main() {
int T; scanf("%d", &T);
while (T--) {
int n; scanf("%d", &n);
int w; scanf("%d", &w);
f[1] = w;
for (int i = 2; i <= n; i++) {
scanf("%d", &w);
f[i] = max(f[i - 1], f[i - 2] + w);
}
printf("%d\n", f[n]);
}
return 0;
}
另一种写法
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int num[N];
int main() {
int T; scanf("%d", &T);
while (T--) {
int n; scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", num + i);
int x = num[0], y = max(num[0], num[1]);
for (int i = 2; i < n; i++) {
int tmp = y;
y = max(x + num[i], y);
x = tmp;
}
printf("%d\n", y);
}
return 0;
}
按照状态机的思路:
将每个 f [ i ] f[i] f[i] 分解成两个状态: f [ i , 0 ] f[i,0] f[i,0] 表示未选择最后一个店铺, f [ i , 1 ] f[i,1] f[i,1] 表示选择最后一个店铺,状态机的每一步都代表原来的一种选法。
集合
f [ i , 0 ] f[i,0] f[i,0]、 f [ i , 1 ] f[i,1] f[i,1]
表示所有走了 i i i 步,且当前位于状态 j ( j = 0 o r 1 ) j(j = 0 \,or\, 1) j(j=0or1)的所有走法价值的最大值
集合划分
-
对于 f [ i , 0 ] f[i,0] f[i,0]
-
左边:最后一步是 0 → 0 0 → 0 0→0, f [ i − 1 , 0 ] f[i-1,0] f[i−1,0],最后一步没有收益
-
右边:最后一步是 1 → 0 1 → 0 1→0, f [ i − 1 , 1 ] f[i-1,1] f[i−1,1],最后一步没有收益
-
-
对于 f [ i , 1 ] f[i,1] f[i,1],最后一步只能从 f [ i − 1 , 0 ] → f [ i , 1 ] f[i-1,0] → f[i,1] f[i−1,0]→f[i,1],所以要求 f [ i , 1 ] f[i,1] f[i,1],就需要先求出 f [ i − 1 , 0 ] f[i-1,0] f[i−1,0],再加上打劫第 i i i 家店铺的收益,故收益为 f [ i − 1 , 0 ] + w [ i ] f[i-1,0] + w[i] f[i−1,0]+w[i]。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int n;
int w[N], f[N][2];
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", w + i);
f[0][0] = 0, f[0][1] = -INF; // 初始化状态机,状态机入口是状态0
for (int i = 1; i <= n; i++) {
f[i][0] = max(f[i - 1][0], f[i - 1][1]);
f[i][1] = f[i - 1][0] + w[i];
}
printf("%d\n", max(f[n][0], f[n][1]));
}
return 0;
}
1.1 打家劫舍系列
1.1.1 打家劫舍 I
解法一:回溯
会超出内存限制,并且数据过大会超时
class Solution {
public:
int rob(vector<int>& nums) {
return dfs(nums.size() - 1, nums);
}
int dfs(int i, vector<int> nums) {
if (i < 0) return 0; // 没有房子可以选择
return max(dfs(i - 1, nums), dfs(i - 2, nums) + nums[i]);
}
};
解法二:回溯 + 保存中间结果 = 记忆化搜索
这里的数组的维度取决于函数 d f s dfs dfs 中传入的参数,传一个参数就只有一维,传两个参数就有两维。
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
vector<int> memo(n, -1); // -1 表示没有计算过
function<int(int)> dfs = [&](int i)->int {
if (i < 0) return 0;
if (memo[i] != -1) return memo[i]; // 表示已经计算过
return memo[i] = max(dfs(i - 1), dfs(i - 2) + nums[i]);
};
return dfs(n - 1);
}
};
时间复杂度:状态数目(n) x 单个状态计算时间( O ( 1 ) O(1) O(1))= O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
解法三:递推
注意:
- 从上往下计算:回溯 / 记忆化搜索
- 从下往上计算:递推
回溯 / 记忆化搜索 翻译成 递推:
- dfs 改成 f 数组
- 递归 改成 循环
- 递归边界 改成 数组初始值
d f s ( i ) = m a x ( d f s ( i − 1 ) , d f s ( i − 2 ) + n u m s [ i ] ) f [ i ] = m a x ( f [ i − 1 ] , f [ i − 2 ] + n u m s [ i ] ) f [ i + 2 ] = m a x ( f [ i + 1 ] , f [ i ] + n u m s [ i ] ) \begin{aligned} dfs(i) &= max(dfs(i - 1), dfs(i - 2) + nums[i]) \\ f[i] &= max(f[i - 1], f[i - 2] + nums[i]) \\ f[i + 2] &= max(f[i + 1], f[i] + nums[i]) \\ \end{aligned} dfs(i)f[i]f[i+2]=max(dfs(i−1),dfs(i−2)+nums[i])=max(f[i−1],f[i−2]+n