你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
// solve表示偷len个店,获得的收益
// 暴力搜索基本上都是递归
// 每次决策状态有两种,递归深度(有n层)--> O(2^n)
int solve(int len, int* nums) {
if (len < 0) return 0;
int mmax = max(nums[len] + solve(len - 2, nums), solve(len - 1, nums));
return mmax;
}
如何优化,减少重复:记忆数组
// O(n)
int solve(int len, int* nums) {
int result[len] = {0};
if (len < 0) return 0;
result[len] = max(nums[len] + solve(len - 2, nums), solve(len - 1, nums));
return result[len];
}
动态规划的本质:递归
动态规划只能解决离散问题,离散问题容易设计状态
递归:原问题(N) -> 子问题(N - 1) -> 原问题(N)
最优子结构:
- 子问题最优决策可导出原问题最优决策
- 处理后效性 -> 无后效性
重叠子问题
- 去冗余
- 空间换时间
动态规划问题共性
套路: 最优,最大, 最小,最长,计数
离散问题: 容易设计状态(整数01背包问题)
最优子结构:N - 1可以推导N
动态规划四个基本步骤:
- 设计暴力算法,找到冗余
- 设计存储状态(一维,二维,三维数组,甚至用Map)
- 递归式(状态转移方程)
- 自顶向下上计算最优解编程方式:递归,倒着想
自低向上上计算最优解编程方式:递推,用for
// 递归转为递推
int solve(int len, int* nums) {
if (len == 0) return 0; // 为空
int result[len] = {0};
result[0] = nums[0]; // 当只有一家店
result[1] = max(nums[0], nums[1]); // 当只有二家店
if (len == 1) return result[0];
for (int i = 2; i < len; i++) { // i = 2 边界
result[i] = max(nums[i] + result[i - 2], result[i - 1]);
}
return result[len];
}
int fib(int n) {
if (n <= 1) return 1;
return fib(n - 1) + fib(n - 2);
}
// 转化为递推
// 递推、递归时间复杂度一样的
int fib(int n) {
f[0] = 0;
f[1] = 1;
f[2] = 1;
for (int i = 3; i <= n; i++) f[i] = f[i - 1] + f[i - 2];
}
小兵向前冲
N * M的棋盘上,小兵要从左下角走到右上角,只能向上或者向右走,问有多少种走法
int f(int n, int m) {
if (n == 0 || m == 0) return 0;
if (n == 1 || m == 1) return 1;
return f(n - 1, m) + f(n, m - 1);
}