入门篇
如何使用动态规划
首先我们要确认是否能使用动态规划
- 问题的答案依赖于问题的规模 ,也就是说这个问题能否被拆分为一个小规模的问题也就是一个F(N)的问题能否转化为F(i)的问题
- F(N) 是否与之前的F(i)答案有关系 也就是能否建立状态转移方程
状态转移方程
- 也就是我们常说的dp[i] 或者dp[i][j]到底是代表什么
- dp[i] 和之前的有什么联系能够用dp [0] --dp[i-1]的值推出dp[i]
确认边界条件
- 也就是dp[0] 是多少
简单
leetcode121 买卖股票的最佳时机
-
定义子问题
dp[i] 表示的就是前i天中 能够获得的最大利润 -
思考递推公式
第i天的时候我们有两种选择
第一种就是 不卖也就是
dp[i] = dp[i-1];
第二种就是卖
dp[i] = nums[i] -minPrices;
所以综合一下就有
dp[i] = Max(dp[i-1],nums[i]-minPrices) -
确认边界条件
dp[0]=0;
minPrices=nums[i]; -
能够做空间优化
由于根据递推公式我们可以得到实际上每个dp[i] 只和dp[i-1]相关 所以我们也就不需要这个数组 直接用 一个变量来保存就可以了
leetcode70爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
- dp[i] 代表 第i层有的方法
- 第二层解题可以怎么上呢
从i-1上一层
从i-2上两层
可以得到 dp[i]=dp[i-1]+dp[i-2] - 边界 dp(1) =1 dp(2)=2
- 优化 找两个变量替代dp[i-1] 和dp[i-2]
int i1 =1;
int i2=2;
int res =0;
for(int i =0;i<n;i++)
{
res = i1+i2;
i1 =i2;
i2 =res;
}
return res ;
leetcode57 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和
1.dp[i]代表以nums[i]元素结尾的的最大值
2. 两种情况 选num[i] 和不选它
dp[i] = Max(dp[i-1]+nums[i],nums[i])
3. 边界dp(1) = num[i];
4. 结果MAX(dp)
5. 优化空间 这个题目我们就无法用一个变量来保存dp[i-1] 因为 注意了我们的dp[i]定义的是以nums[i] 元素结尾的最大值 所以dp[n] 可能就不是我们最终要的结果 所以,要保存过程中的解
leetcode198 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
- dp[i] 表示前i个的打劫能抢到的最高金额
- nums[i] 有两种情况 打劫和不劫
dp[i] = Max(dp[i-2]+nums[i],dp[i-1]) - dp(0) = nums(0) dp(1)= Max(nums(1),nums(0))
- 优化 可以用两个变量保存因为我们求的就是前n个的最高金额
中等难度
中等难度的问题一般
- dp数组也由一维变成 了二维 循环也由一维变成二维
- dp的动态表达也更加有难度
leetcode392判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
-
dp[i][j] 就代表 s的前i个字符串 能不能匹配 t数组的前j个字符
-
如果 s[i] == t[j] 那么也就是说 dp[i][j] =dp[i-1][j-1](因为 s中的i字符已经和t中的j字符匹配了,所以看dp[i][j] 也就是看dp[i-1][j-1]
)
如果s[i]!=t[j] 那么dp[i][j] =dp[i][j-1]; -
边界条件 s[0] ==t[0] —>dp[0][0] =true;
反之就是dp[0][0] =false; -
优化 应该也可以 用一个一维数组去替换一个二维数组
第二种思路比较难理解
-
dp[i][j] 代表的是t中从i 开始字母j第一次出现的位置(这其中字母就是a-z)也就是说dp[i][a]…dp[i][z]的一个i*26的二维数组
-
t中没有s[i] 返回false
if(dp[add][s[i] - 'a'] == m){ return false; }
否则直接跳到t中s[i]第一次出现的位置之后一位
add = dp[add][s[i] - 'a'] + 1;
for(int i = 0;i<n;i++){ //对s进行遍历
//t中没有s[i] 返回false
if(dp[add][s[i] - 'a'] == m){
return false;
}
//否则直接跳到t中s[i]第一次出现的位置之后一位
add = dp[add][s[i] - 'a'] + 1;
}
- 从后向前遍历 初始化边界条件,dp[i][j] = m表示t中不存在字符j
for(int i=0;i<26;i++){
dp[i][m] = m;
}
leetcode 62 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
- dp[i][j] 代表的就是机器人到达num[i][j] 位置的几种方法
- 很明显 dp[i][j] = dp[i][j-1]+dp[i-1][j] 但是考虑边界问题
边界上方和左方的到达路径可能
上方 dp[i][j] = d[i][j-1]
左方 dp[i][j] = d[i-1][j] - 很明显的是上方和左方的格子dp[i][j] 全部都是1
- 优化问题 我们实际上用到的只有当前行cur 和之前的行 pre
因此只需要保留当前行与上一行的数据 (在动态方程中,即pre[j] = dp[i-1][j]),两行,空间复杂度O(2n)
Arrays.fill(cur,1);
for (int i = 1; i < m;i++){
for (int j = 1; j < n; j++){
cur[j] = cur[j-1] + pre[j];
}
pre = cur.clone();
}
return pre[n-1];
- 优化2
cur[j] += cur[j-1], 即cur[j] = cur[j] + cur[j-1] 等价于思路二–>> cur[j] = pre[j] + cur[j-1],