1.动态规划的本质: 递归
2.原问题(N) - >子问题(N-1)->原问题(N)
3.最优子结构:
- 子问题最优决策可导出原问题最优决策
- 无后效性
4.重叠子问题:
- 去冗余
- 空间换时间 (时空复杂度的分析)
- 问题共性:
- 套路:最优、最大、最小、最长、计数
- 离散问题:整数01背包问题
- 最优子结构。N-1可以推导出N
6.基本步骤
1.设计暴力算法,找到冗余
2.设计存储状态(一维、二维、三维数组甚至用Map)
3.递归式(状态转移方程)
4.自底向上计算最优解(编程方式)
例题1. leetcode 198 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
接下来以这个题为例子入手:
按照步骤一步一步来:
1⃣️. 设计暴力算法,找到冗余. 在这里通过递归进行暴力求解
我们可以从后往前来看
2⃣️设计存储状态(下面的暴力算法不用,后面的动态规划做法。开辟了一个一维数组保存中间结果)
3⃣️递归式 状态转移方程
4⃣️自底向上计算最优解。(写法3)
假如从最后一家开始抢,对于每一家店nums[i],小偷都有两种选择 抢和不抢
若是抢,由于不能抢连续的两家店,则小偷无法抢nums[i-1]; nums[i] + nums[i-2]
若是不抢,则金额可以是 nums[i-1]
首先我们可以写一个暴力递归版本 时间复杂度O(2^n) 空间复杂度O(1)
写法1 暴力算法(超时)
class Solution {
public int solve(int idx,int[] nums){
//判断边界条件
if(idx < 0 ) return 0;
//选择抢nums[idx]的方式
int method1 = nums[idx] + solve(idx-2,nums);
//选择不抢nums[idx]的方式
int method2 = solve(idx-1,nums);
//从两个方式中选择最大的那个方式
int res = Math.max(method1,method2);
return res;
}
public int rob(int[] nums) {
return solve(nums.length-1,nums);
}
}
下面画图分析一下递归过程
我们可以发现 在执行递归的过程中 有许多调用是重复的,这样就会使得时间开销大大增大,以指数级别增长
为了提高时间效率,我们可以采用牺牲部分空间的方式,即可以开辟出来一份空间来保存这些重复的中间结果,
使得这些中间结果只计算一次.
对于这道题我们可以开辟一个数组dp[]来保存中间结果 dp[i]表示到第i个店时已经抢到的最优金额
从后往前搜索的版本. 时间复杂度O(n) 空间复杂度O(n)
写法2:
class Solution {
private int []dp;
public int solve(int idx,int[] nums){
//边界条件
if(idx < 0){
return 0;
}
//记忆化搜索
if(dp[idx] != -1){
return dp[idx];
}
//抢idx
int method1 = nums[idx] + solve(idx-2,nums);
//不抢idx
int method2 = solve(idx-1,nums);
dp[idx] = Math.max(method1,method2);
return dp[idx];
}
public int rob(int[] nums) {
dp = new int[nums.length];
//进行初始化
for(int i = 0; i < dp.length;i++){
dp[i] = -1;
}
return solve(nums.length-1,nums);
}
}
写法3:
从前往后搜索的版本. 时间复杂度O(n) 空间复杂度O(n)
class Solution{
private int[] dp;
public int solve(int idx,int [] nums){
if(idx >= nums.length){
return 0;
}
if(dp[idx]!= -1){
return dp[idx];
}
//选择第一个
int method1 = nums[idx] + solve(idx+2,nums);
//不选择第一个
int method2 = solve(idx+1,nums);
dp[idx] = Math.max(method1,method2);
return dp[idx];
}
public int rob(int[] nums){
dp = new int [nums.length];
for(int i=0;i<nums.length;i++){
dp[i] = -1;
}
return solve(0,nums);
}
}
作者:lhsjohn 喜欢的话谢谢点赞~