动态规划
- 动态规划算法集合
- 算法理解
- 刷题记录
- leetcode-5 最长回文子串🌟🌟
- leetcode-53 最大子序列和🌟
- leetcode-62. 不同路径🌟🌟
- [leetcode-63. 不同路径 II 🌟🌟](https://leetcode-cn.com/problems /unique-paths-ii/)
- [leetcode-64. 最小路径和 🌟🌟](https://leetcode-cn.com/problems/minimum-path-sum/)
- [leetcode-1014. 最佳观光组合🌟🌟](https://leetcode-cn.com/problems/best-sightseeing-pair/)
- [leetcode-121. 买卖股票的最佳时机🌟](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/)
- [leetcode-122. 买卖股票的最佳时机 II 🌟🌟](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
- [leetcode-309. 最佳买卖股票时机含冷冻期🌟🌟](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
- [leetcode-714. 买卖股票的最佳时机含手续费🌟🌟](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
- [leetcode-139. 单词拆分🌟🌟](https://leetcode-cn.com/problems/word-break/)
- [leetcode-413. 等差数列划分🌟🌟](https://leetcode-cn.com/problems/arithmetic-slices/)
- [leetcode-264. 丑数 II🌟🌟](https://leetcode-cn.com/problems/ugly-number-ii/)
- [leetcode-96. 不同的二叉搜索树🌟🌟](https://leetcode-cn.com/problems/unique-binary-search-trees/)
- [leetcode-931. 下降路径最小和🌟🌟](https://leetcode-cn.com/problems/minimum-falling-path-sum/)
- [leetcode-120. 三角形最小路径和🌟🌟](https://leetcode-cn.com/problems/triangle/)
- [做的不好--1314. 矩阵区域和](https://leetcode-cn.com/problems/matrix-block-sum/)
- [做的不好--304. 二维区域和检索 - 矩阵不可变](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/)
- [leetcode-32. 最长有效括号🌟🌟🌟
动态规划算法集合
算法理解
动态规划算法的本质是将一个大问题分解为小问题来解决,但是和分治法不同,分治法分开的问题都是独立的,他们都可以有自己的最优解,然后再进行组合。但是动态规划的子问题大多是重合的,比如说最简单的斐波那契数列,就是动态规划的一个实例 f ( 7 ) = f ( 6 ) + f ( 5 ) f(7)=f(6)+f(5) f(7)=f(6)+f(5), f ( 6 ) = f ( 5 ) + f ( 4 ) f(6)=f(5)+f(4) f(6)=f(5)+f(4).如果是递归调用的话, f ( 5 ) f(5) f(5)要计算多次,但是如果我们把这些算出来的字问题的解记录下来,然后之后再使用的时候就不用再算了。动态规划一般都是这样的,就是将问题划分为子问题,然后求出子问题的解,记录下来,自底向上迭代,找出全局的最优解。
动态规划的难点在于,向上迭代的过程,这就要我们找到状态转移方程,还是最简单的例子,斐波那契数列的转移方程非常的简单
f
(
n
)
=
f
(
n
−
1
)
+
f
(
n
−
2
)
f(n)=f(n-1)+f(n-2)
f(n)=f(n−1)+f(n−2)
非常简单就可以实现了.但是很多比较复杂的问题的状态转移方程不容易找,需要多加练习,把这类问题的精髓找到,就可以拿下dp,加油。
刷题记录
leetcode-5 最长回文子串🌟🌟
解题思路
首先要考虑将这个问题划分为子问题,那么一个字符串的最长回文子串哪里找,肯定从子串里面找啊。那么问题就变成了我们要在所有的子串里面找回文串了,我们先判断这个子串是不是回文,如果是的话就记录长度和位置。但这样还是没有找到迭代的方法,我们也不能枚举,那么太暴力了。
现在想一下子串和子串之间的关系,一个串只有一个字符那肯定就是个回文,但是如果一个子串是回文串,那么他怎么样能联系到别的子串呢。考虑这个字符串 a b a a b abaab abaab,如果一个字符串是个回文串,在他基础上怎样能在获得一条更长回文串,那就是给他两边增加字符呀。例如第一个b出现时,他肯定是个回文,b的左边和右边都是a,那么就可以得到一条新的回文串了。这样我们的状态转移方程马上就出来了。用一个二维的矩阵来存放信息 d p [ n ] [ n ] dp[n][n] dp[n][n], n n n是这个字符串的长度, d p [ i ] [ j ] 用 来 表 示 i 到 j 部 分 是 不 是 回 文 dp[i][j]用来表示i到j部分是不是回文 dp[i][j]用来表示i到j部分是不是回文,这样我们就可以在这个基础上写状态转移方程了。
状态转移方程
d p [ i ] [ j ] = 1 , i > = j d p [ i ] [ j ] = ( d p [ i + 1 ] [ j − 1 ] ) & & ( s [ i ] = = s [ j ] ) dp[i][j]=1, i>=j\\ dp[i][j]=(dp[i+1][j-1])\&\&(s[i]==s[j]) dp[i][j]=1,i>=jdp[i][j]=(dp[i+1][j−1])&&(s[i]==s[j])
代码实现
class Solution {
public:
string longestPalindrome(string s) {
int n=s.size();
bool dp[n][n];
//初始化 j大于等于i就设为1
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
dp[i][j]=true;
}
}
//用来记录回文串位置
int max=0;
int l1;
l1=0;
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
//状态转移方程
dp[j][i]=(s[i]==s[j])&&(dp[j+1][i-1]);
//如果找到一个回文串,进行记录
if(dp[j][i]==true){
if(max<(i-j)){
max=i-j;
l1=j;
}
}
}
}
return s.substr(l1,max+1);
}
};
leetcode-53 最大子序列和🌟
(要求是连续的,要是不连续也行那就都吧正数加起来就行了,,)
解题思路
还没写代码,但是先写下思路,这个我觉得和回文串什么的有异曲同工之妙,那个是连续子串的性质,这个是连续子序列的性质,那么也可以想做是在一个序列上扩展,碰到最大值以后在记录下来就好了。 d p [ n ] [ n ] dp[n][n] dp[n][n]来记录子序列的和, d p [ i ] [ j ] dp[i][j] dp[i][j]就可以代表i到j的和,那么像是 d p [ i − 1 ] [ j ] 或 者 d p [ i ] [ j + 1 ] dp[i-1][j]或者dp[i][j+1] dp[i−1][j]或者dp[i][j+1]就和它有关系。换句话说 d p [ i ] [ j ] 可 以 从 d p [ i + 1 ] [ j ] 和 d p [ i ] [ j − 1 ] 推 出 来 dp[i][j]可以从dp[i+1][j]和dp[i][j-1]推出来 dp[i][j]可以从dp[i+1][j]和dp[i][j−1]推出来
👆是智障思路,写完了也发现了,我特么这不是枚举了么
👇是正确思路💗
就是这样只考虑前面的序列,那么一个序列nums中的最大连续子序列和就是看要不要nums[n],直接写下状态方程吧. d p [ 2 ] [ n ] dp[2][n] dp[2][n], d p [ 0 ] [ n ] 表 示 不 要 这 个 数 , 然 后 之 前 的 最 大 子 序 列 和 dp[0][n]表示不要这个数,然后之前的最大子序列和 dp[0][n]表示不要这个数,然后之前的最大子序列和, d p [ 1 ] [ n ] 表 示 要 这 个 数 然 后 结 合 上 之 前 的 dp[1][n]表示要这个数然后结合上之前的 dp[1][n]表示要这个数然后结合上之前的
状态转移方程
d p [ 0 ] [ n ] = m a x ( d p [ 0 ] [ n − 1 ] , d p [ 1 ] [ n − 1 ] ) / / 不 要 自 己 了 就 拿 前 面 的 最 大 的 , 后 面 也 不 能 续 d p [ 1 ] [ n ] = m a x ( n u m s [ n ] + d p [ 1 ] [ n − 1 ] , n u m s [ n ] ) / / 要 了 自 己 就 得 和 前 面 相 加 , 然 后 也 得 和 前 面 也 不 要 比 较 一 下 dp[0][n]=max(dp[0][n-1],dp[1][n-1]) \ \ \ \ \ //不要自己了就拿前面的最大的,后面也不能续\\ dp[1][n]=max(nums[n]+dp[1][n-1],nums[n])\ \ \ // 要了自己就得和前面相加,然后也得和前面也不要比较一下 dp[0][n]=max(dp[0][n−1],dp[1][n−1]) //不要自己了就拿前面的最大的,后面也不能续dp[1][n]=max(nums[n]+dp[1][n−1],nums[n]) //要了自己就得和前面相加,然后也得和前面也不要比较一下
初始化就是注意负数,写一下代码吧
代码实现
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n=nums.size();
if(n==1)return nums[0];
int dp[2][n];
dp[0][0]=INT_MIN;
dp[1][0]=nums[0];
for(int i=1;i<n;i++){
dp[0][i]=max(dp[0][i-1],dp[1][i-1]);
dp[1][i]=max(dp[1][i-1]+nums[i],nums[i]);
}
return max(dp[0][n-1],dp[1][n-1]);
}
};
leetcode-62. 不同路径🌟🌟
解题思路
这是一道比较简单的动态规划问题,简单在,他的状态方程很简单,很容易就想到了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9AwRog1t-1652627090262)(/Users/autreyhepburn/Desktop/截屏2021-11-28 下午11.42.08.png)]
在这样的方格里面找路径的问题,而且规定了从左上角移动到右下角,还只能向下或者向右。那么一个方格的来向就只有两种了,那么到达某一方格的路径数就是到达他左边格子的路径数再加上到达上面方格的路径数就可以了。用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示到达某一方格的路径数,那么需要做的初始化就是最左边一列和最上边一行的格子的路径数都为1,这很显然,然后就可以写出状态转移方程了。
状态转移方程
KaTeX parse error: Undefined control sequence: \ at position 71: …+dp[i][j-1]\ \ \̲ ̲
代码实现
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[m+1][n+1];
for(int i=1;i<=m;i++)dp[i][1]=1;
for(int i=1;i<=n;i++)dp[1][i]=1;
for(int i=2;i<=m;i++){
for(int j=2;j<=n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}
};
[leetcode-63. 不同路径 II 🌟🌟](https://leetcode-cn.com/problems /unique-paths-ii/)
解题思路
这个也还是路径问题,就是62题加一个障碍而已,需要多判断一下,别的也没什么。注意下面两个问题。
一、初始化的时候,最左列和最上面的行,如果出现了一个障碍,那么后面都到达不了了。
二、在求某一个格子的路径数时,如果这个格子是有障碍的,那么到达此格子的路径数为0
状态转移方程
d p [ i ] [ j ] = 0 o b s t a c l e [ i ] [ j ] = = 1 d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + d p [ i − 1 ] [ j ] o b s t a c l e [ i ] [ j ] = = 0 dp[i][j]=0 \ \ \ \ obstacle[i][j]==1\\ dp[i][j]=dp[i][j-1]+dp[i-1][j] \ \ \ \ obstacle[i][j]==0 dp[i][j]=0 obstacle[i][j]==1dp[i][j]=dp[i][j−1]+dp[i−1][j] obstacle[i][j]==0
代码实现
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m=obstacleGrid.size(); //行数
int n=obstacleGrid[0].size();//列数
int dp[m][n]; //状态矩阵
//需要全部设置为0
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
dp[i][j]=0;
}
}
//初始化
for(int i=0;i<m;i++){
if(obstacleGrid[i][0]==1)break;
dp[i][0]=1;
}
for(int i=0;i<n;i++){
if(obstacleGrid[0][i]==1)break;
dp[0][i]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(obstacleGrid[i][j]!=1){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
};
leetcode-64. 最小路径和 🌟🌟
解题思路
这个问题的思路和前面的路径数有异曲同工之妙,最大的相同在于他们都只能向下或者向右移动,那么 d p [ i ] [ j ] dp[i][j] dp[i][j]也是和 d p [ i − 1 ] [ j ] 和 d p [ i ] [ j − 1 ] dp[i-1][j]和dp[i][j-1] dp[i−1][j]和dp[i][j−1]有关。题目要求的是最小的路径,那么就是在左边和上面的两个格子之间选择路径和更小的就可以了。
状态转移方程
KaTeX parse error: Undefined control sequence: \ at position 171: …grid[i][j] \ \ \̲ ̲
代码实现
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
int dp[m][n];
dp[0][0]=grid[0][0];
for(int i=1;i<m;i++)dp[i][0]=dp[i-1][0]+grid[i][0];
for(int i=1;i<n;i++)dp[0][i]=dp[0][i-1]+grid[0][i];
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
};
leetcode-1014. 最佳观光组合🌟🌟
解题思路
这个题目一开始没想到dp,很简单枚举就可以,不过要O(n2),dp的话只要O(n)。
dp的思路是要找到最优子结构,我就一直在想,这种数组的一般就是dp[i]和dp[i-1]有关系,然后要注意对dp数组的定义,最近时间做dp题我发现,对dp不同理解的解法会不同,而且很多时候有多解的情况。
我把dp[i]设置为i前面可以提供的最大数字,因为他在求和以后要减去间距,那么value[0]=8,它在走到i=3的时候就只能看作是5了,这样一直将目前可提供的最大值保存。然后结果的话也得每次都求最大值 m = m a x ( m , d p [ i − 1 ] + v a l u e [ i ] ) m=max(m,dp[i-1]+value[i]) m=max(m,dp[i−1]+value[i]),最终函数返回m就可以了。
状态转移方程
d p [ i ] = m a x ( d p [ i − 1 ] − 1 , v a l u e [ i ] − 1 ) m = m a x ( m , d p [ i − 1 ] + v a l u e [ i ] ) dp[i]=max(dp[i-1]-1,value[i]-1)\\ m=max(m,dp[i-1]+value[i]) dp[i]=max(dp[i−1]−1,value[i]−1)m=max(m,dp[i−1]+value[i])
代码实现
//实现的时候我直接用滚动数组了
class Solution {
public:
int maxScoreSightseeingPair(vector<int>& values) {
int n=values.size();
// int dp[n];
// dp[0]=values[0];
int mx=values[0]-1;
int m=0;
for(int i=1;i<n;i++){
m=max(m,values[i]+mx);
mx=max(mx-1,values[i]-1);
}
return m;
}
};
leetcode-121. 买卖股票的最佳时机🌟
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
解题思路
这个是股票这系列问题里最简单的一个,就是找到最大的差值就可以了,没啥别的。要想找到最大的差值,就得知道前面最小的值是多少,所以需要维护一个这样的值,还有最大的差值也需要维护一下。虽然是动态规划,但是其实滚动数组更好理解。
状态转移方程
使用
d
p
[
i
]
dp[i]
dp[i]来表示下标i之前的最小值
d
p
[
i
]
=
m
i
n
(
n
u
m
s
[
i
]
,
d
p
[
i
−
1
]
)
;
d
p
[
0
]
=
n
u
m
s
[
0
]
;
m
=
m
a
x
(
m
,
n
u
m
s
[
i
]
−
d
p
[
i
−
1
]
)
;
dp[i]=min(nums[i],dp[i-1]);\\ dp[0]=nums[0];\\ m=max(m,nums[i]-dp[i-1]);
dp[i]=min(nums[i],dp[i−1]);dp[0]=nums[0];m=max(m,nums[i]−dp[i−1]);
代码实现
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
//if(n<=1)return 0;
int m=0;
int mi=prices[0];
for(int i=1;i<n;i++){
m=max(m,prices[i]-mi);
mi=min(mi,prices[i]);
}
return m;
}
};
leetcode-122. 买卖股票的最佳时机 II 🌟🌟
给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
解题思路
这个问题是一个很典型的动态规划题目 ,而且需要考虑两个状态,就是要考虑此时购买了股票或者此时没有持有股票两种情况,使用 d p [ i ] [ 0 ] dp[i][0] dp[i][0]来表示当前不持有股票的收益,使用 d p [ i ] [ 1 ] dp[i][1] dp[i][1]表示当前持有股票的收益,当前的最大收益就是 m a x ( d p [ i ] [ 0 ] , d p [ i ] [ 1 ] ) max(dp[i][0],dp[i][1]) max(dp[i][0],dp[i][1]).当前的状态和之前的状态是有联系的。
首先看当前不持有股票的收益:
当前不持有股票可以有两种情况得到,1。前一个状态是不持有股票,那我现在也不持有顺下来。2.前面是持有的但是没卖呢,我现在卖了,我就也不持有了。
然后是当前持有股票的收益
1.前一个状态是持有股票,我现在也不卖就可以了
2.前一个状态不持有股票,但是我现在就是要买一股,那么就是持有了
根据上面说的情况,可以写出状态转换方程
状态转换方程
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e [ i ] ) d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e [ i ] ) 初 始 化 : d p [ 0 ] [ 0 ] = 0 , d p [ 0 ] [ 1 ] = − p r i c e [ 0 ] dp[i][0]=max(dp[i-1][0],dp[i-1][1]+price[i])\\ dp[i][1]=max(dp[i-1][1],dp[i-1][0]-price[i])\\ 初始化:dp[0][0]=0,dp[0][1]=-price[0] dp[i][0]=max(dp[i−1][0],dp[i−1][1]+price[i])dp[i][1]=max(dp[i−1][1],dp[i−1][0]−price[i])初始化:dp[0][0]=0,dp[0][1]=−price[0]
代码实现
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int dp[n][2];
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<n;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return max(dp[n-1][0],dp[n-1][1]);
}
};
leetcode-309. 最佳买卖股票时机含冷冻期🌟🌟
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
解题思路
这个题目和122题基本相同,就是微调一下。主要变化是状态转移的过程,之前是紧接着的i-1到i,现在不行了,买的时候得看下前一天有没有卖掉,所以不能从 d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i−1][0]得到了,需要从现在至少得是i-2到i了,那就是初始化也要多加一点。
状态转换方程
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e [ i ] ) d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 2 ] [ 0 ] − p r i c e [ i ] ) dp[i][0]=max(dp[i-1][0],dp[i-1][1]+price[i])\\ dp[i][1]=max(dp[i-1][1],dp[i-2][0]-price[i])\\ dp[i][0]=max(dp[i−1][0],dp[i−1][1]+price[i])dp[i][1]=max(dp[i−1][1],dp[i−2][0]−price[i])
初始化:
d
p
[
0
]
[
0
]
=
0
,
d
p
[
0
]
[
1
]
=
−
p
r
i
c
e
[
0
]
d
p
[
1
]
[
0
]
=
m
a
x
(
0
,
p
r
i
c
e
[
1
]
−
p
r
i
c
e
[
0
]
)
d
p
[
1
]
[
1
]
=
m
a
x
(
d
p
[
0
]
[
1
]
,
−
p
r
i
c
e
[
1
]
)
;
dp[0][0]=0,dp[0][1]=-price[0]\\ dp[1][0]=max(0,price[1]-price[0])\\dp[1][1]=max(dp[0][1],-price[1]);
dp[0][0]=0,dp[0][1]=−price[0]dp[1][0]=max(0,price[1]−price[0])dp[1][1]=max(dp[0][1],−price[1]);
代码实现
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n<=1)return 0;
int dp[n][2];
dp[0][0]=0;
dp[0][1]=-prices[0];
dp[1][0]=max(0,prices[1]-prices[0]);
dp[1][1]=max(dp[0][1],-prices[1]);
for(int i=2;i<n;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-2][0]-prices[i]);
}
return max(dp[n-1][0],dp[n-1][1]);
}
};
leetcode-714. 买卖股票的最佳时机含手续费🌟🌟
给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
解题思路
这个就是122题多付一笔钱而已,如出一辙
状态转换方程
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e [ i ] − f e e ) d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e [ i ] ) 初 始 化 : d p [ 0 ] [ 0 ] = 0 , d p [ 0 ] [ 1 ] = − p r i c e [ 0 ] dp[i][0]=max(dp[i-1][0],dp[i-1][1]+price[i]-fee)\\ dp[i][1]=max(dp[i-1][1],dp[i-1][0]-price[i])\\ 初始化:dp[0][0]=0,dp[0][1]=-price[0] dp[i][0]=max(dp[i−1][0],dp[i−1][1]+price[i]−fee)dp[i][1]=max(dp[i−1][1],dp[i−1][0]−price[i])初始化:dp[0][0]=0,dp[0][1]=−price[0]
代码实现
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n=prices.size();
int dp[n][2];
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<n;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]-fee);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return max(dp[n-1][0],dp[n-1][1]);
}
};
leetcode-139. 单词拆分🌟🌟
给你一个字符串 s 和一个字符串列表 wordDict 作为字典,判定 s 是否可以由空格拆分为一个或多个在字典中出现的单词。
说明:拆分时可以重复使用字典中的单词。
解题思路
这个题就是要判断当前结尾为一个单词是否在字典中出现,并且抛去后面这个单词前面是不是满足可拆分的条件。这样就可以了,我的解法先记录下来字典中所有单词的长度,以便于以后先前探索的时候不用便利很多次。然后这个问题其实就是一个状态传递的问题了。
状态转换方程
d p [ i ] = d [ i − l j ] ∣ ∣ m a p [ s t r . s u b s t r ( i − l j , l j ) ] = = 1 j = 1 , 2 , 3.... dp[i]=d[i-lj]||map[str.substr(i-lj,lj)]==1 j=1,2,3.... dp[i]=d[i−lj]∣∣map[str.substr(i−lj,lj)]==1j=1,2,3....
代码实现
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
set<int> se;
unordered_map<string,int> m;
for(string str:wordDict){
se.insert(str.size()); //记录字典中单词的可能长度
m[str]++;
}
int n=s.size();
bool dp[n];
for(int i=1;i<n;i++)dp[i]=0;
dp[0]=0;
for(int i=0;i<n;i++){
for(int l:se){
if(l<=i+1){
if(i==l-1){
dp[i]=(dp[i]||(m[s.substr(i-l+1,l)]==1));
}else{
dp[i]=(dp[i]||(m[s.substr(i-l+1,l)]==1&&dp[i-l]));
}
}
}
}
return dp[n-1];
}
};
leetcode-413. 等差数列划分🌟🌟
如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。
例如,[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。
给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组 个数。子数组 是数组中的一个连续序列。
解题思路
我这题发现和别人的题解也有点不同,有时候这个状态的理解不一样就答案不同。典型的一个数组的动态规划,当前状态和前一个位置直接相关,这类的在动态规划里面比较简单。
我用 d p [ i ] dp[i] dp[i]记录以i结尾的等差数列的长度,知道这个长度就可以知道以它结尾可以获得几个子数组,例如1,2,3,4.以3结尾可获得1个,因为子数组长度至少为三。以四结尾有2,3,4和1,2,3,4两种。总共就是三种。
这样每次求出dp[i]以后,进行一次计算得到子数组个数然后求和就可以了。
状态转换方程
dp表示等差数列长度
d
p
[
i
]
=
d
p
[
i
−
1
]
+
1
,
n
u
m
[
i
]
−
n
u
m
[
i
−
1
]
=
=
n
u
m
[
i
−
1
]
−
n
u
m
[
i
−
2
]
d
p
[
i
]
=
2
,
n
u
m
[
i
]
−
n
u
m
[
i
−
1
]
!
=
n
u
m
[
i
−
1
]
−
n
u
m
[
i
−
2
]
dp[i]=dp[i-1]+1,num[i]-num[i-1]==num[i-1]-num[i-2]\\ dp[i]=2,num[i]-num[i-1]!=num[i-1]-num[i-2]
dp[i]=dp[i−1]+1,num[i]−num[i−1]==num[i−1]−num[i−2]dp[i]=2,num[i]−num[i−1]!=num[i−1]−num[i−2]
代码实现
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& nums) {
int n=nums.size();
if(n<3)return 0;
if(n==3){
if(nums[1]-nums[0]==nums[2]-nums[1])
return 1;
else
return 0;
}
int sup=nums[1]-nums[0];
int sum=0;
int l=2;
for(int i=2;i<n;i++){
if(nums[i]-nums[i-1]==sup){
l++;
}else{
sup=nums[i]-nums[i-1];
l=2;
}
sum+=(l-2);
}
return sum;
}
};
leetcode-264. 丑数 II🌟🌟
给你一个整数
n
,请你找出并返回第n
个 丑数 。丑数 就是只包含质因数
2
、3
和/或5
的正整数。
解题思路
这个题目的解题思路非常巧妙,很自然的一种思路是利用优先队列,每次取出最小的丑数就可以了,但是这个动态规划的实现更有意思。
使用一个属猪dp[n]表示第n个丑数。
哟们可以用三个游标p2,p3,p5来记录下一个要乘2,3,5的位置,每次从dp[p2]*2,dp[p3]*3,dp[p5]*5中选一个最小的填入,然后给对应的游标向后一位。这样计算n次就可以了。
状态转移方程
d
p
[
i
]
=
m
i
n
(
d
p
[
p
2
]
∗
2
,
d
p
[
p
3
]
∗
3
,
d
p
[
p
5
]
∗
5
)
dp[i]=min(dp[p2]*2,dp[p3]*3,dp[p5]*5)
dp[i]=min(dp[p2]∗2,dp[p3]∗3,dp[p5]∗5)
代码实现
class Solution {
public:
int nthUglyNumber(int n) {
int dp[n+1];
dp[1]=1;
int p2=1,p3=1,p5=1;
for(int i=2;i<n+1;i++){
int m2=dp[p2]*2;
int m3=dp[p3]*3;
int m5=dp[p5]*5;
int m=min(m2,min(m3,m5));
if(m2==m)p2++;
if(m3==m)p3++;
if(m5==m)p5++;
dp[i]=m;
}
return dp[n];
}
};
leetcode-96. 不同的二叉搜索树🌟🌟
解题思路
这个题目是比较简单的,思路很容易就想到了。一个二叉搜索树他的中序遍历是有一定顺序的,我们计算每一个节点作为根节点的可以得到的二叉搜索树个数相加就可以,子树的形态个数只和子树的节点个数有关,可以用dp数组来记录。
状态转移矩阵
d p [ n ] = ∑ d p [ i ] ∗ d p [ n − i − 1 ] dp[n]=\sum dp[i]*dp[n-i-1] dp[n]=∑dp[i]∗dp[n−i−1]
代码实现
class Solution {
public:
int numTrees(int n) {
int dp[n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<n+1;i++){
dp[i]=0;
for(int j=0;j<i;j++){
dp[i]+=(dp[j]*dp[i-j-1]);
}
}
return dp[n];
}
};
leetcode-931. 下降路径最小和🌟🌟
给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。
解题思路
这题就是那种最动态规划的题了,很题干已经告诉转移矩阵怎么写了,没啥思路可以说。
状态转移矩阵
d p [ n ] [ i ] = m i n ( d p [ n − 1 ] [ i − 1 ] , d p [ n − 1 ] [ i ] , d p [ n − 1 ] [ i + 1 ] ) + c o s t [ n ] [ i ] ; 注 意 边 界 最 左 和 最 右 要 特 殊 一 点 就 可 以 了 初 始 化 第 一 层 d p [ 0 ] [ i ] = c o s t [ 0 ] [ i ] dp[n][i]=min(dp[n-1][i-1],dp[n-1][i],dp[n-1][i+1])+cost[n][i]; \\注意边界最左和最右要特殊一点就可以了 \\初始化第一层 \\dp[0][i]=cost[0][i] dp[n][i]=min(dp[n−1][i−1],dp[n−1][i],dp[n−1][i+1])+cost[n][i];注意边界最左和最右要特殊一点就可以了初始化第一层dp[0][i]=cost[0][i]
代码实现
这里是一个需要二位矩阵的实现,可以优化为只需要一维矩阵
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& matrix) {
int m=matrix.size();
int n=matrix[0].size();
for(int i=m-2;i>=0;i--){
for(int j=0;j<n;j++){
if(j==0){
matrix[i][j]=min(matrix[i+1][j],matrix[i+1][j+1])+matrix[i][j];
}else if(j==n-1){
matrix[i][j]=min(matrix[i+1][j],matrix[i+1][j-1])+matrix[i][j];
}else{
matrix[i][j]=min(matrix[i+1][j],min(matrix[i+1][j-1],matrix[i+1][j+1]))+matrix[i][j];
}
}
}
int min=INT_MAX;
for(int i=0;i<n;i++){
if(min>matrix[0][i])min=matrix[0][i];
}
return min;
}
};
leetcode-120. 三角形最小路径和🌟🌟
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
解题思路
这题就是上面那个正方形的变形,那个可以有三个方向,这个只有两个方向。而且这个从上到下,只需要返回dp[0][0]就可以了,不像上面还得求个最小值。
状态转移方程
d p [ n ] [ i ] = m i n ( d p [ n + 1 ] [ i + 1 ] , d p [ n − 1 ] [ i ] ) + c o s t [ n ] [ i ] ; dp[n][i]=min(dp[n+1][i+1],dp[n-1][i])+cost[n][i]; dp[n][i]=min(dp[n+1][i+1],dp[n−1][i])+cost[n][i];
代码实现
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int m=triangle.size();
for(int i=m-2;i>=0;i--){
int n=triangle[i].size();
for(int j=0;j<n;j++){
triangle[i][j]=min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
}
}
return triangle[0][0];
}
};
做的不好–1314. 矩阵区域和
做的不好–304. 二维区域和检索 - 矩阵不可变
[leetcode-32. 最长有效括号🌟🌟🌟
给你一个只包含
'('
和')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
解题思路
状态转移方程
KaTeX parse error: Expected 'EOF', got '&' at position 19: …[i][j]=dp[i][k]&̲&dp[k+1][j]||s[…
代码实现
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int m=triangle.size();
for(int i=m-2;i>=0;i--){
int n=triangle[i].size();
for(int j=0;j<n;j++){
triangle[i][j]=min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
}
}
return triangle[0][0];
}
};