1.动态规划模板

1.确定dp数组(dp table)以及下标的含义

2.确定递推公式

3.dp数组如何初始化

4.确定遍历顺序

5.举例推导dp数组

1.1 动态规划和回溯的区别

什么时候用动态规划,什么时候用回溯:

如果一个题的答案是一个值,E.g. 3,true or false ,则用动态规划

如果题目中有让列出所有结果的可能性:回溯

1.2 动态规划和谈心的区别

DP 和贪心的区别:

DP : y i = f(yi-1) 根据上一个状态推断

贪心:yi = f(yi) 通用的式子

2.LeetCode 相关题目

2.1_509斐波那契数

LeetCode题目链接

2.1.1 算法描述

1.确定 dp 数组以及其下标含义

dp[i] 的定义为:第 i 个数的斐波那契数为 dp[i]

2.确定推导公式

根据题目可知推导公式:

dp[i] = dp[i-1]+dp[i-2]

3.dp 数组如何初始化

题目已给出

dp[1] = 0 ; dp[1] = 1

4.确定遍历顺序

必须要先知道前面的值才能得到后面的值,所以是从前向后

5.举例推导 dp 数组

N为10的时候,dp数组应该是如下的数列:

0 1 1 2 3 5 8 13 21 34 55

2.1.2 C++ 代码实现

1.完整代码

class Solution {
public:int fib(int n) {if(n==0) return 0;if(n==1) return 1;vector<int>dp(n+1); // 因为要保存 0 ,所以最后个数要加上 1dp[0] = 0;dp[1] = 1;for(int i = 2;i<=n;i++){dp[i] = dp[i-1]+dp[i-2];}return dp[n];}
};

2.代码简化

因为最后取的只是一个值,cur 状态的判断只需要它前面的那个值,所以使用两个变量就可以得到cur 的值

class Solution {
public:int fib(int n) {if(n==0) return 0;if(n==1) return 1;vector<int>dp(2); // 只保存两个值就可以实现dp[0] = 0;dp[1] = 1;for(int i = 2;i<=n;i++){int cur = dp[0]+dp[1]; // 0 保存 cur-2 , 1 保存 cur-1dp[0] = dp[1];dp[1] = cur;}return dp[1];}
};

2.1.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N)

但是在递归情况下时空复杂度:

时间复杂度:O(n^2)

空间复杂度:O(n) 栈所需要空间

2.2_70爬楼梯

2.2.1 算法描述

0阶:0种

1阶:1种

1;

2阶:2种

1+1;2

3阶:3种

1+1+1;2+1;1+2

4阶:5种

1+1+1+1;2+1+1;1+2+1;1+1+2;2+2

其中 2+1+1 , 1+2+1,1+1+2 时由上一步 2+1 再添了 1 后排列组合生成的

5阶:8种

1+1+1+1+1;2+1+1+1;1+2+1+1+;1+1+2+1+1;1+1+1+2;1+2+2;2+1+2;2+2+1

。。。。。。

1.确定dp数组及其下标的含义

dp[i]的定义为:爬到第 i层有多少种方法

2.确定推导公式

对于 i 来说,i-1 的方法个数再跳一节就是 i ; i-2 的方法个数再跳两节就是 i 阶的方法个数。所以最后总的方法个数为为两种情况相加

dp[i] = dp[i-1]+dp[i-2]

3.dp 数组如何初始化

dp[1] = 1

dp[2] = 2

4.确定遍历顺序

从前向后遍历

5.举例推导 dp 数组

上面已经有了举例

2.2.2 C++ 代码实现

1.普通代码

class Solution {
public:int climbStairs(int n) {if(n==1) return 1;if(n==2) return 2;vector<int> dp(n+1);dp[1] = 1;dp[2] = 2;for(int i=3;i<=n;i++){dp[i] = dp[i-1]+dp[i-2];}return dp[n];}
};

2.优化代码

class Solution {
public:int climbStairs(int n) {if(n==1) return 1;if(n==2) return 2;int dp[3];dp[1] = 1;dp[2] = 2;for(int i=3;i<=n;i++){int cur = dp[1]+dp[2];dp[1] = dp[2];dp[2] = cur; }return dp[2];}
};

3.完全背包方法

2.2.3 时空复杂度

非优化方法:

时间复杂度:O(N)

空间复杂度:O(N)

2.3_746使用最小花费爬楼梯

LeetCode 题目链接

2.3.1 算法描述

1.确定 dp 数组每个值的含义

dp[i] 代表:到达第 i 阶花费的最小体力

2.确定递推顺序

dp[i] 的值可以由两个值得到 ,是 dp[i-1] 和 dp[i-2] ,判断哪个更小就累加哪个

关键:选择出更小的那个出发点

min(dp[i-1],dp[i-2])

3.dp 数组如何初始化

dp[0] = 0;

dp[1] = 1;

4.确定遍历顺序

从前向后

5.举例推导数组

省略。。。

2.3.2 C++ 代码实现

1.普通方法

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {vector<int> dp(cost.size());dp[0] = cost[0];dp[1] = cost[1];for (int i = 2; i < cost.size(); i++) {dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];}// 注意最后一步可以理解为不用花费,所以取倒数第一步,第二步的最少值return min(dp[cost.size() - 1], dp[cost.size() - 2]);}
};

2.优化方法

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int dp[2];dp[0] = cost[0];dp[1] = cost[1];for(int i =2;i<cost.size();i++){int cur = min(dp[0],dp[1])+cost[i];dp[0] = dp[1];dp[1] = cur;}return min(dp[0],dp[1]);}
};

2.3.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

2.4_62不同路径

LeetCode 题目链接

2.4.1 算法描述

image-20211105180834892

绿色的框 = 橙框1向下走+橙框3向右走

1.确定 dp 每个元素代表什么

dp[i] [j] 代表从 (0,0) 走到 (i,j) 这个位置有几种方法

2.dp 的推导公式

除了最上面一排和最左边一排

dp [i] [j] = dp [i-1] [j]+dp [i] [j-1]

从上面逼近的所有情况再往下走+从左边逼近的所有情况再往右走

3.dp 的初始化

对最上面一排和最左边一排进行初始化为 1

4.遍历方式

从前向后

5.举例推导数组

如上图所示

2.4.2 C++ 代码实现

class Solution {
public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m,vector<int>(n,0));for(int i =0; i<m;i++){for(int j =0;j<n;j++){if(i==0 || j==0) dp[i][j] = 1;else dp[i][j] = dp[i-1][j]+dp[i][j-1];}}return dp[m-1][n-1];}
};

2.4.3时空复杂度

时间复杂度:O(M*N)

空间复杂度:O(M*N)

2.4.4 知识扩展

1.如何初始化一个一维数组

vector<int> res(n,0);

2.如何初始化二维数组

vector<vector<int>> res(n,vector<int>(n,0)); 

为什么可以这么初始化,看一下 vector 的源码就可以知道

image-20211105183753470

2.5_63不同路径2

LeetCode 题目连接

2.5.1 算法描述

这个题和 63 题的方法一样,有两个地方不一样:

1.对于第一行和第一列的判断

**易错点:**这里不能根据当前坐标是否有障碍而初始化 0,1 ,因为他们属于边上的格子,如果他的前一个没法走通后面的所有格子即使没有障碍物也走不通

所以如果前面的格子走不通就 break;

2.对中间部分每个格进行判断

这个判断和上一个题一样,只不过在赋值之前要判断当前各自没有遮挡,如果遮挡了就不进行任何处理。不遮挡才用公式取值

2.5.2 C++ 代码实现

class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m = obstacleGrid.size();int n = obstacleGrid[0].size();vector<vector<int>> dp(m, vector<int>(n, 0));// 使用 for 循环单独赋值for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {if (obstacleGrid[i][j] == 1) continue;dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}return dp[m - 1][n - 1];}
};

2.5.3时空复杂度

时间复杂度:O(M*N)

空间复杂度:O(M*N)

2.6_343整数拆分

LeetCode 题目连接

2.6.1 算法描述

1.dp 中的每个元素代表什么

dp[i] 代表第 i 个元素的最大拆分乘积

2.dp[i] 中的每个值如何得到

当 i>2 时,对于正整数 i 拆分出的第一个正整数为 j ,则有以下两种方案:

将 i 拆分成 j 和 (i-j) 的和,然后不再进行切分了,那么当前值的乘积结果就为 j*(i-j)

将 i 拆分成 j 和 (i-j) 的和,但是 (i-j) 再继续拆分,此时的乘积是 j*dp[i-j]

i之前的每一个数都有可能成为i 的最后一个乘积,所以要一个个列举

所以就要使用双重 for 循环找到这个 j

所以当 j 固定时,有 dp[i] = max(j * (i-j),j * dp[i-j])

由于 j 的取值范围是 1~i-1,每次不断的更新 dp[i] 中的值,直到把这个 j 找到,

转换成公式就为:

image-20211106152223110

3.dp 的初始化

i = 0 的时候就不用初试化了,0 乘任何数都为 0 ,所以只初始化 dp[1] 和 dp[2] 即可

dp[2] =1

4.确定遍历顺序

从前往后遍历

5.举例推导 dp 数组

举例 i=4 时:

jj-ij*(i-j)j*dp[i-j] dp[3]=2,dp[1]=1
1332
2244(取最大值)
3132

2.6.2 C++ 代码实现

class Solution {
public:int integerBreak(int n) {// 1. 定义 dp 数组vector<int> dp(n+1);// 2. dp 数组初始化dp[1] = 1;dp[2] = 1;for(int i =2;i<=n;i++){for(int j=1;j<i;j++){ // 从 [1~i-1] 逐个判断dp[i] = max(dp[i],max(j*(i-j),j*dp[i-j]));}}return dp[n];}
};

易错:

1在第二层 for 循环时判断的范围是 1~i-1 这样就需要给 dp[1] 进行初始化

2.7_96不同的二叉搜索树

LeetCode 题目链接

2.7.1 算法描述

基础是 node1 和 node2 的两个子树的结构,node3 就可以推导出来

image-20211122213500978

image-20211106165012863

将搜索树的所有情况都列出,每个节点都可以当头结点

①当node1 做为头结点时,剩下的所有节点只能放在 node 1 的右子树。node2 和 node3 的布局个数就是 node 1 和 node2 的布局个数

②当 node2 作为头节点时,其右边只有 node3 这一个节点,左边是 node1 的布局个数。

②当node3作为头结点时,剩余的所有节点只能放在 node3 的左侧,同理布局个数就是 node2 的布局个数

dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量

他们都属于同一个树,所以是相乘,不是相加。最后求总个数时将多种结果相加

元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量

元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量

元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量

根据搜索二叉树的性质,每个子节点都可以如下划分,下面是 4 个节点的布局:

image-20211123152957949

从上面的公式可以推导出,整体是一个双重 for 循环,要判断每个节点的同时每个节点都要从 node1~noden 分别做头结点去判断

dp[i] += dp[j-1]*dp[i-j]  // i 之前*i之后

动态规划5步曲:

1.dp[i] 表示什么

dp[i] 表示当 n=i 时二叉搜索树的结构(布局)个数

2.如何得到 dp[i]

由上面的推导

dp[i] += dp[j-1]*dp[i-j]

3.dp 数组如何初始化

dp[0] = 1 ; dp[1] = 1; dp[2]=2

易错点:

这里的 dp[0] 初始化的值是 1,不是 0,node 0 也算是一种结构。而且如果初始化是 0 那后面很多值点乘后都是 0 了

4.遍历的顺序

从前向后遍历

5.举例推导 dp 数组

上面又对 n=3 举例,当 n=4 时比较难画

2.7.2 C++ 代码实现

class Solution {
public:int numTrees(int n) {vector<int>dp(n+1,0);dp[0] = 1;dp[1] = 1;for(int i =2;i<=n;i++){for(int j =1;j<=i;j++){dp[i]+=dp[j-1]*dp[i-j];}}return dp[n];}
};

易错点:

题目中给出的范围是 1~xx 所以在进行初始化的时候 >1 的值就不要进行初始化

2.7.3 时空复杂度

时间复杂度:O(n^2)

空间复杂度:O(n)

2.8_01背包

背包分类:

只看 01 背包和完全背包就好

image-20211123162336612

2.8.1_二维数组算法描述

B 站大佬讲解

image-20211106181327569 image-20211106174454248

前提:背包中每个编号的物体只能放一件

1所在行:

包里只能放 0,1 这两个物品,包的容量不断变大,但是也只能放 01 两个物品,并且两个物品最多放一件

Eg:在坐标 (1,4) :

包里可以放编号为 01 的两个物品,但是包的容量只有 4 这么大。

经过判断是可以放下 1 的。这时候有两种选择,放 1 还是不放 1 。

不放 1 :只放 0 ,对应的表是 (0,4) 这个坐标,这个坐标的价值是 0

放1 :首先包的容量要 -2,价值+3 ;然后还能放 0,所以对应的是 (0,2) 这个坐标的价值是0;总价值是 3

对比放 1 和不放 1 两种情况,最大值是 放 1 ,价值是 3,写在坐标 (1,4) 中

Eg2:在坐标(3,7)

包里可以放编号 0,1,2,3 这三种物品,包的容量是 7

经过判断 3 是可以放下的。这个时候有两种选择放 3 还是不放 3

不放3:就相当于只放 0,1,2 这种情况,对应的坐标是 (2,7) ,价值为 7

放3:首先包的容量 -4,容量变为 3,价值累加 5。但是可以放(2,3) 的价值,也就是 4。那么这 4 最后累加 5 的总值是 9

Eg 通式:

Step1:首先判断坐标 j 是否可以放这个物品

Step2:求放与不放哪个更大

dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i])

图下图所示,要判断红框的值,则分别判断绿框的值谁大

image-20211108155535826 image-20211123170556632

暴力解如何实现:

这里有三个物品,每个物品的重量都是 {1,3,4} ;如果使用暴力解就是不断枚举可以组合的方案

{1} ; {1,3};{1,3,4};{3};{3,4};{4} ,每个物品都有 N 种结果,那么所有物品就有 N*N 种方案。这很明显是一个 O(N^2) 的方案;当然这其中我们也可以对 {1,3,4} , {3,4} 等不满足背包容量的放按进行剪枝处理

但是如果这里使用 DP 就可以将其转换成 O(N*M) 的方案,与此同时也可以进行剪枝操作

动态规划5步曲:

1.dp[i] [j] 表示什么

dp[i] [j] 表示从下标为 0-i的物品里任意取,放进容量为j的背包,价值总和最大是多少

(这里是背包容量,不是背包个数!!)

2.如何得到 dp[i]

dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i])

3.dp 数组如何初始化

首先从dp[i] [j]的定义出发,如果背包容量j为0的话,即dp[i] [0],无论是选取哪些物品,背包价值总和一定为0

状态转移方程 dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。初始化方程如下所示:

image-20211106180829518

4.遍历顺序

(1)先遍历背包再遍历物品

(2)先遍历物品再遍历背包

5.举例推导 dp 数组

上图灰色表格

2.8.2 二维数组C++ 代码实现

using namespace std;
#include<vector>
#include<iostream>
void test_2_wei_bag_problem1() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagWeight = 4; // 背包里面最多可以放重量为 4 的物品// 二维数组vector<vector<int>> dp(weight.size(), vector<int>(bagWeight + 1, 0));// 初始化for (int j = weight[0]; j <= bagWeight; j++) {dp[0][j] = value[0];}// weight数组的大小 就是物品个数for(int i = 1; i < weight.size(); i++) { // 遍历物品for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量if (j < weight[i]) dp[i][j] = dp[i - 1][j];else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}}cout << dp[weight.size() - 1][bagWeight] << endl;
}    int main() {test_2_wei_bag_problem1();
}

2.8.3 时空复杂度

假设有 N 个数据,背包容量为 M

时间复杂度:O(N*M)

空间复杂度:O(N*M)

2.8.4 滚动数组算法描述

上面使用了一个二维数组存放数据,代表编号 [0,i-1] 任意存放在不同背包重量时最大价值是多少。

但是往往题目只要求我们求出在某个编号下背包的容量,就没有必要将前面所有的数据都进行存储。

其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i] [j] = max(dp[i] [j], dp[i] [j - weight[i]] + value[i]);

vector 的  index 值就是背包的重量

所以这个时候用到了 一维滚动数组

1.确定 dp[j] 的含义

d[j] 的含义:当前编号下,包的容量为 j 时所存放的最大价值。

2.递推公式

这样就会存在一个覆盖过程:

当物体放入背包:d[j] = d[j-weight[i]]+value[i]

当物品不放入背包:d[j] = d[j] 也就是原先的 d[i-1] [j]

max(d[j],d[j-weight[i]]+value[i])

3.dp 数组如何初始化

从上面的递推公式可以看出只需要将 dp[j] = 0 就可以了,也就是说一开始一位数组所有的数都设为 0

举例:

物品重量价值
物品0115
物品1320
物品2430

下面就以画图的方式展现 “如何进行覆盖”

最外层循环遍历背包,内层循环遍历物品,内层循环从后向前。下图中横向是背包重量,纵向是可以向背包内放的编号

Step1:将背包初始化

一维数组每个值都初始化 0

image-20211109082735560

Step2:遍历 0 号物品

从后向前进行遍历

image-20211109082815673

dp[4] = max(dp[4],dp[4-weight[0]]+value[0])

公式解读:

物品 0 有放入与放入两种情况:

不放入则继续保持原样即 dp[j]

放入:因为包内容量是固定的,现在想再加一个物品,所以要先将包内 **“一般等重物” **拿出,拿出的这个物品只是和物品 0 等重,但不是物品 0 。,所以放入的公式为: dp[j-weight[0]] + value[0] 其中 weight[0] 就是物品 0 的一般等重物

所以推导出来上面那个公式

。。。。。。按照上面相同的方法将物品0 放入背包 。。。。。。

Step3:将物品 2 放入背包

image-20211109082953300

这里遍历背包重量时还是从后向前遍历,所以先遍历重量为 4 的情况

根据公式 dp[j] = max(dp[j],dp[j-weight[i]]+value[i]) 得出

dp[4] = max(dp[4],dp[4-weight[2]]+value[2])

物品1 在重量为 4 时可以选择放入,可以不放入

放入:这时候包里面是满的,必须要将一部分一般等重物拿出再放入,公式如下:

dp[4] = dp[4-weight[1]]+value[1] dp[4] = 30

不放入:背包里面的原价值不变

dp[j] = dp[4]=35

这时候发现拿出的一般等重物竟然都是贵的东西,所以还不如不拿

面试常考题1: 为什么遍历背包的时候(内层 for 循环)是从后往前

下面是滚动数组的核心代码:

for(int i=0;i<weight.size();i++){for(int j=weight[i];j>=bagweight;j++){ // 遍历到 weight[i] 是因为再往前数 i 被判断过了dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);}}

就拿物品0的放入来说,在这个阶段只能放编号 0 ,并且 0 只能放入一次:

将 i =0 ,j = 1,2 代入代码中正向遍历,下面是编号 0 物品放入的情况:

dp[1] = dp[1-weight[0]]+value[0] = 0+15

dp[2] = dp[2-weight[0]]+value[0] = 15+15 = 30

所以 dp[2] 很奇怪,这时候只能放 0 号物品,-weight[0] 操作已经将等重物拿出来了,而且这个等重物就是 0 ,但是包的重量是 15 ,说明包里还有编号 0 ,与包中只能存在一个 0 冲突

反向 for 循环:

dp[2] = dp[2-weight[1]]+value[0] = 0+15

dp[1] = dp[1-weight[1]]+value[0] = 0+15

反向循环后将编号 0 的物品拿出就不会出现包里有多余的编号0存在

通过上图的红绿箭头就可以看出依赖关系

image-20211111180439901

解释:

从后向前循环,每次取得的状态不会和之前取得的状态重合,保证每个物品只放一次

对于二维数组:因为对于二维dp,dp[i] [j]都是通过上一层即dp[i - 1] [j]计算而来,所以就不存在上图中 b 物品已经放入的情况

面试常考题2: 遍历物品顺序和背包顺序是否可以颠倒

答案:不能

解释:

因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品

不会覆盖

2.8.5 一维数组 C++ 代码实现

using namespace std;
#include<vector>
#include<iostream>
void test_beibao(){vector<int>weight = {1,3,4};vector<int>value = {15,20,30};int bagWeight = 4;// 背包vector<int>dp(bagWeight+1,0);for(int i=0;i<weight.size();i++){for(int j=bagWeight;j>=weight[i];j--){ // 遍历到 weight[i] 是因为再往前数 i 被判断过了dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);}}cout<<dp[bagWeight]<<endl;
}
int main(){test_beibao();
}

2.8.6 时空复杂度

时间复杂度:O(N*M)

空间复杂度:O(N)

2.7_416分割等和子集

LeetCode 题目链接

2.7.1 算法描述

1.dp[j] 表示什么

dp[j] 表示,数组当前能容纳 j 的总和情况下,背包的真实重量是多少

2.dp[j] 的推导公式

dp[j] = max(dp[j],dp[j-nums[i]]+nums[i])

3.dp 数组如何初始化

dp[0] = 0

4.确定遍历顺序

滚动数组使用逆序遍历

5.举例推导 dp 数组

【1,5,5,11】

dp 的大小为 11 +1

dp[0] = 0

dp[1] = 1

dp[2] = 1

dp[3] = 1 出现背包大小大于元素和的情况

dp[6] = 6

dp[11] = 11 最后判断数组中元素的和是否可以达到 sums/2 ,如果没有达到也说明不能整除

2.7.2 C++ 代码实现

class Solution {
public:bool canPartition(vector<int>& nums) {int sums = 0;// 首先判断能否将数组进行均分for(int i =0;i<nums.size();i++)  sums+=nums[i];if(sums%2==1) return false;int bagWeight=sums/2;// 因为元素个数和元素值决定 sums 的结果不会大于 20000,所以定义一个 10001 的背包vector<int> dp(bagWeight+1,0); // 将元素一个个的放入背包for(int i = 0;i<nums.size();i++){for(int j = bagWeight;j>=nums[i];j--)dp[j] = max(dp[j],dp[j-nums[i]]+nums[i]); // 选择放这个数还是不放}// 判断集合中的元素是否可以凑成 target if(dp[bagWeight]==bagWeight) return true;return false;}
};

最后还需要添加判断,判断数组中的值是否可以达到要求

易错点:

求出中间值后对于背包来说最后的结果还要再 +1 才是背包的重量

2.7.3 时空复杂度

时间复杂度:O(N^2)

空间复杂度:O(N)

2.8_1049最后一块石头的重量2

LeetCode题目链接

2.8.1 算法描述

1.如何粉碎使得粉碎到最后石头的剩余的最小

找两个重量接近的石头进行粉碎

2.化解为背包问题

现在石头的总和为 sums 。我们可以将一堆石头划分成重量非常接近的两小堆。让每一个小堆的 target 接近于 sums/2

也就是求怎么放石头可以让背包内的物品重量最大接近于 sums/2

3.动规五部曲

①dp[j] 代表什么

dp[i] 代表背包容量为 j 时,包内放得重量

②dp[j] 的推导公式

dp[j] = max(dp[j],dp[j-stone[i]]+stone[i]);

③dp[i] 的初始化

dp[0] = 0

④遍历顺序

遵循滚动数组逆向遍历

⑤举例推导dp数组

【2,7,4,1,8,1】

dp[0] = 0

dp[1] = 0

dp[2] = 2

dp 数组的赋值顺序是如下图所示

image-20211124161643696

6.为什么我们只需要算一堆的重量

bagWeight 是向下取整 ,所以 bagWeight *2 <sums

sums-bagWeight *2 会得到一个大于 0 的值,这个值就是相撞的损耗

2.8.2 C++ 代码实现

lass Solution {
public:int lastStoneWeightII(vector<int>& stones) {// 计算背包中放入的总重量int sums = 0;for(int i =0 ;i<stones.size();i++) sums+=stones[i];int bagWeight = sums/2;vector<int> dp(bagWeight+1,0);for(int i = 0;i<stones.size();i++){for(int j = bagWeight;j>=stones[i];j--) dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);}return sums-dp[bagWeight]-dp[bagWeight];}
};

2.8.3 时空复杂度

时间复杂度:O(M*N) 双重 for 循环

空间复杂度:O(N) 石头个数

2.9_494目标和

2.9.1 算法描述

1.如何转换成 01 背包问题

在一个公式中有两堆数字,left 和 right ,这两堆数字都是无符号的数字,在下面的式子中,left :1,1,1,1 right(绝对值) :1

image-20211108144916443

推导公式1:

left-right=target;right = target -left

推导公式2:

其中 target 是固定的,数组中所有元素的和 sums 也是固定的,下面将 right 替换出来

left-(sums-left) = target --> left = (target+sums)/2

总结:

本问题化简成在集合中 nums 找出和为 left 的组合,转换为背包问题就是:

有几种生成 left 目标值的方式

2.两种无解的情况

sums = left+right

target = left-right

sums+target = 2left

if((sums+target)%2==1) return 0;

target= < sums

if(abs(target>sums)) return 0;

3.动规五部曲

①确定 dp 数组以及下标的含义

dp[j]:生成值为 j 的 left ,有几种生成方式

这里的 left 最大是等于 target 的

②确定递推公式

所以 dp[j] 中存放的就是在 [0,i-1] 这几个编号的数中凑成了和为 j 有几种方法

dp[j] = dp[j]+dp[j-nums[i]] 分别对应不将 i 放入 left 和将 i 放入 left

③dp 数组初始化

dp[0] = 1:装满容量为0的背包,有1种方法,就是装0件物品

如果 dp[0] =0 则后面的值全部是 0

④确定遍历顺序

内循环颠倒

⑤举例推导 dp 数组

image-20211108153900651

4.遇到求种类个数的问题

如果下次遇到相同求个数的问题还是用公式:

dp[j]+=dp[j-nums[i]];

这里还是是否将 nums [i] 放入公式中的情况。分为放和不放

放入 nums[i] :dp[j-nums[i]] left 公式中就要相应减少一定的数值才能保证 target 不变。因为求得是个数所以后面不用再加 nums[i]

假设 nums[i] 等于 2 ,dp[3] = 4 ,有四种方法生成等式 3 ,这时候想要生成等式 5 ,只需要将 2 加在公式中,这只是一种方法,所以还是 dp[j-nums[i]] 方法个数没变

不放入 nums[i] :left 式子中不放入 i ,则组成的公式个数为 dp[j]

最后求得是情况的个数:将放和不放的情况个数进行相加

2.9.2C++ 代码实现

public:int findTargetSumWays(vector<int>& nums, int target) {int sums = 0;// 判断是否由解决方案for(int i=0;i<nums.size();i++) sums+=nums[i];// 没有解决方案if(abs(target)>sums) return 0; if((target+sums)%2==1) return 0;int bagWeight = (target+sums)/2;vector<int> dp(bagWeight+1,0);dp[0] = 1;for(int i=0;i<nums.size();i++){for(int j =bagWeight;j>=nums[i];j--){dp[j]+=dp[j-nums[i]];}}return dp[bagWeight];}
};

2.9.3 时空复杂度

时间复杂度:O(N*M) M 为 left 的和,即背包的容量

空间复杂度:O(M)

2.10_474一和零

LeetCode题目链接

2.10.1算法描述

首先这是一个 01 背包的问题。因为每个数组中的每个物品只能拿一次。但是对包中放的物品是有要求的

因为对于背包内的物品是有要求的,所以可以定义二维数组用来确定向背包中放什么

感觉第二维就像是背包的另一个夹层,夹层背包要设置一个二位 dp

1.确定dp[i]是什么

dp[i] [j]:最多有i个0和j个1的strs的最大子集的大小为dp[i] [j]

这里的 j 就是代表的 01 的个数

dp[i] [j]就是题目

2.确定递推公式

dp[i] [j] 就可以是 dp[i - zeroNum] [j - oneNum] + 1

如果减法是在 【】 内部减的就代表我们要将这个元素放进背包了,这里是在内部减的,所以就是将这个元素放入背包了

所以公式就是:

dp[i] [j] = max(dp[i] [j], dp[i - zeroNum] [j - oneNum] + 1);

因为是求个数所以最后是+1不是加数量

3.dp 数组如何初始化

dp[0] [0] = 0

一般在 0 的位置上都是 0

4.遍历顺序

外部从前往后,内部从后向前

2.10.2 C++ 代码实现

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<int>> dp(m+1,vector<int>(n+1,0));for(string str:strs){ // 遍历每个物品int oneNum=0,zeroNum=0; // 求得当前这个物品 0,1 的个数for(char c:str){if(c=='0') zeroNum++;else oneNum++;}// 向 dp 中放入该物品for(int i=m;i>=zeroNum;i--){for(int j =n;j>=oneNum;j--){dp[i][j] = max(dp[i][j],dp[i-zeroNum][j-oneNum]+1);}}}return dp[m][n];}
};

易错点:

在动态的推导公式中 ,如果写的是 ++ 会报错

dp[i-zeroNum][j-oneNum]+1 

2.10.3 时空复杂度

时间复杂度:O(M * N * S)

空间复杂度:O(M*N)

2.11_完全背包

2.11.1 算法描述

完全背包和 01 背包的不同点在于完全背包中每个物品都可以放入无限次,01背包只能放一次

完全背包需要正向遍历第二个循环

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = weight[i]; j <=bagWeight ; j++) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}
}

2.11.2 代码实现

1.先遍历物品

using namespace std;
#include<vector>
#include<iostream>
void wanquanbeibao(){vector<int>weight = {1,3,4};vector<int>value = {15,20,30};int bagWeight = 4;vector<int>dp(bagWeight+1,0);for(int i=0;i<weight.size();i++){for(int j=weight[i];j<=bagWeight;j++){ // 从 weight[i] 开始的目的就是保证可以将物品放下dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);}}cout<<dp[bagWeight]<<endl;
}
int main(){wanquanbeibao();return 0;
}

2.先遍历背包

using namespace std;
#include<vector>
#include<iostream>
void wanquanbeibao(){vector<int>weight = {1,3,4};vector<int>value = {15,20,30};int bagWeight = 4;vector<int>dp(bagWeight+1,0);for(int j =0;j<=bagWeight;j++){for(int i=0;i<weight.size();i++){// 判断物品是否可以放入if(j-weight[i]>=0) dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);}}cout<<dp[bagWeight]<<endl;
}
int main(){wanquanbeibao();return 0;
}

先遍历背包时 i,j 代表的寓意没有变,在放入物品之前先判断是否可以放入

2.11.3 面试中常问问题

最后,又可以出一道面试题了,就是纯完全背包,要求先用二维dp数组实现,然后再用一维dp数组实现,最后在问,两个for循环的先后是否可以颠倒?为什么? 这个简单的完全背包问题,估计就可以难住不少候选人了。

2.12_518 零钱兑换2

2.12.1 算法描述

1.dp[j] 代表什么

dp[j] :在当前 index 情况下,零钱值为 j ,有几种换零钱的方式

2.dp[j] 的推导公式

这个题很像 494 目标和,dp[j] = dp[j] + dp[j-nums[i]]

即不加这个硬币和加上这个硬币换零钱的方式个数

3.dp[j] 的初始化

dp[0] = 1;因为零钱为 0 时有 1 种方式,就是啥也不放

4.遍历方式

外部硬币,内部背包。而且是完全背包

5.举例 dp 数组

image-20211109155945894

2.12.2 C++ 代码实现

class Solution {
public:int change(int amount, vector<int>& coins) {vector<int>dp(amount+1,0);dp[0] = 1;for(int i =0;i<coins.size();i++){for(int j=coins[i];j<=amount;j++){dp[j]+=dp[j-coins[i]]; // 不放+放}}if(dp[amount]==0) return 0; // 判断是否可以凑出金额return dp[amount];}
};

易错点:

这里只是将 dp[0] = 1 ,千万别把所有 dp 的值都设置为 1

2.12.3 时空复杂度

时间复杂度:O(M*N)

空间复杂度:O(M ) M 为 amount 的值

2.12.4 知识扩展–为什么不能先背包再物品

如果先物品,那么在不同的背包中出现的都是相同的物品,因为都属于一个大的 for 循环,所以是组合问题。

如果是先背包,那么第二层 for 循环每次都从0开始,假设说放2 ,那么每次放的2都被认作是不同的2。所以就变成了排列问题

image-20211109203252837

2.判断是否可以凑足零钱

这里存在没有办法凑够零钱的情况,如果没有办法凑够零钱那么 dp[amount] 的值一定是没有被赋值过的初始化的值,所以只需要在遍历的最后判断一下即可

if(dp[amount]==0) return 0;

2.14_377 组合总和 4

2.14.1 算法描述

image-20211109210053233

这个题和上面的题不一样,这个题的答案顺序是重复的,所以需要将背包放在外面将物品放在里面

2.14.2 C++ 代码实现

class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(target+1,0);dp[0]=1;for(int j = 1;j<=target;j++){for(int i =0;i<nums.size();i++){if(j>=nums[i]&&dp[j]<INT_MAX-dp[j-nums[i]]) dp[j]=dp[j]+dp[j-nums[i]]; // 防止 dp[j]+dp[j-nums[i]] 过大else dp[j] = dp[j];}}return dp[target];}
};

易错:

需要注意的是 dp[j]+dp[j-nums[i]] 有可能会是一个很大的数,所以这里要先判断一下,如果这个数很大就不进行操作了

dp[j]<INT_MAX-dp[j-nums[i]]

2.2_70爬楼梯(完全背包)

2.2.1 算法描述

这个题有点类似于将零钱放入钱包的问题。假设现在有一个可以放 n 个硬币的钱包,硬币分为 1块,2块两种。从题目中可以看出这个钱是一种排列,在钱包能放 3 块时,有 (1,2) 和 (2,1) 都算对,所以是一个 排列问题

当一次可以爬 1~m 个楼梯时可以转换为向背包中放入 1~m 个零钱问题

2.2.2 C++ 代码实现

class Solution {
public:int climbStairs(int n) {vector<int>dp(n+1,0);dp[1] = 1;if(n==1) return 1; // 只有一个数的时候要单独处理,否则会有数组越界移异常dp[2] = 1;for(int j = 0;j<=n;j++){for(int i =1;i<=2;i++){if(j>=i) dp[j]+=dp[j-i];}}return dp[n];}
};

易错点:

1.因为初始化的时候

2.2.3 时空复杂度

时间复杂度:O(M*N) M:一共爬几阶,N:一次可以迈几阶

空间复杂度:O(M)

2.15_322零钱兑换

2.15.1 算法描述

1.dp[j] 代表什么

dp[j] :凑足总额为 j 所需钱币的个数最少为 dp[j] 个

2.确定地推公式

有两种可能将背包凑够重量,就是这个硬币是放还是不放,我们需要在放和不放当中找处硬币个数最少的情况

放:因为求得是个数所以如果将 cosin[i] 放入的话需要将 dp[j-coins[i]] 结果的个数 +1

不放:直接是 dp[j]

最后公式为: min(dp[j],dp[j-cosin[i]]+1)

求最大个数时要分为 放和不放两种情况的相加和最后的结果一定情况最多

最少情况那就选 放或者不放其中一个最小值就好

要想将 coins[i] 放入背包需要添加的判断:

如果背包的重量能放下 cosin[i] ,并且 cosin[i] 所依附的结果是可以成功的(否则求 dp[j-conins[i]] 是没有结果的)

if(i-cosin[j]>=0&&dp[j-consin[i]]!=INT_MAX)

3.dp 数组初始化

因为要求最小个数,所以初始化时每个元素一定要是最大

vector<int>dp(amount+1,INT_MAX); 
dp[0]=0; // 这个值是真实值

4.确定遍历顺序

这个题求得是个数,所以不在乎能有几种组合或者排列的方式。所以这个题 for 循环的顺序没有影响

5.举例 dp 数组:完全背包中的排列组合问题

2.15.2C++ 代码实现

class Solution {
public:int coinChange(vector<int>& coins, int amount) {// 初始化 dp 数组vector<int>dp(amount+1,INT_MAX);dp[0] = 0;for(int j = 0;j<=amount;j++){for(int i = 0;i<coins.size();i++){if(j-coins[i]>=0&&dp[j-coins[i]]!=INT_MAX) // 当前背包还能放下这个硬币&&j-coins[i]是可以放下的dp[j] = min(dp[j-coins[i]]+1,dp[j]);}}if(dp[amount]==INT_MAX) return -1;return dp[amount];}
};

2.15.3 时空复杂度

时间复杂度:O(M*N)

空间复杂度:O(M)

2.16_279完全平方数

2.16.1 算法描述

1.dp[j] 代表什么

dp[j] :当物品恰好将背包塞满的情况下需要多少个物品

2.dp 的遍历公式

这个题同上面的题一样,分为将 i*i 放入背包与不放入

放入:dp[j-i*i]+1

不放入:dp[j]

需要注意这里的拿一般等重物时拿出的值为 i*i 

3.dp的初始化

dp[0] = 0,因为上面给的数 i 是从 1 开始的

dp[1] =1

4.dp 的遍历顺序

因为是数个数所以无关乎遍历顺序

5.举例 dp 数组

2.16.2 C++ 代码实现

1.先遍历物品

class Solution {
public:int numSquares(int n) {vector<int> dp(n+1,INT_MAX);dp[0] = 0;dp[1] = 1;for(int j =0;j<=n;j++){for(int i =1;i*i<=j;i++){ // 这里循环的条件需要注意dp[j] = min(dp[j],dp[j-i*i]+1); // 从包中拿出的物品质量}}return dp[n];}
};

看这里的条件,在 for 循环中已经判断是否可以将 i*i  放入 j 中了

2.先遍历背包

class Solution {
public:int numSquares(int n) {vector<int>dp(n+1,INT_MAX);dp[0] = 0;dp[1] = 1;for(int i=1;i<n;i++){for(int j = i*i;j<=n;j++){dp[j] = min(dp[j],dp[j-i*i]+1);}}return dp[n];}
};

2.16.3 时空复杂度

时间复杂度:O(M*N)

空间复杂度:O(M)

2.17_139单词拆分

2.17.1 算法描述

如何转换成动态规划题:

字符串 s 是背包,单词就是物品,问物品是否可以将背包装满,并且单词可以重复使用,所以是完全背包

背包的重量就是 s 不断变化的长度

1.dp[j] 代表什么

dp[i] 为下标指向字符串中的 i 时,[i,j] 是否在 set 中,是一个 bool 类型的值

2.确定递推公式

这个是需要不断的往前判断

当往背包中放 lee 时,先判断 lee 是否在 set 中,如果不在,则判断 ee 是否在,然后判断 e 是否在

什么时候 dp[j] 为 true

①包内的字母属于 Set 子集&&②从包内开始地方 i 之前的那一堆字母组成的单词也在 Set 中

3.dp 的初始化

dp[0] = true

 **从遍历公式看出,这个子串的值要想是 true 其前面的那个值也得是 true **

当 s 表示 Null 时 {} 中也是 Null 所以从这个角度解释也是 true

4.数组的遍历方式

只是求是否出现过,所以排列还是组合不用在意

5.举例推导 dp 数组

在放入背包单词时一个一个放单词,但是在判断子串会先剔除前面的字符

i=0i=1i=2i=3i=4i=5i=6i=7
j=1l
j=2le(这时候背包只能放两个物品)e
j=3leeeee
j=4leet(true)
break;
j=5leetceetcetctcc
j=6leetcoeetcoetcotcocoo
j=7leetcodeetcodetcodtcodcododd
j=8leetcodeeetcodeetcodetcodecodeodedee

2.17.2 C++ 代码实现

class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());vector<bool> dp(s.size() + 1, false);dp[0] = true;for (int j = 1; j <= s.size(); j++) {   // 遍历背包for (int i = 0; i < j; i++) {       // 遍历物品string word = s.substr(i, j - i); //substr(起始位置,截取的个数)if (wordSet.find(word) != wordSet.end() && dp[i]) {dp[j] = true;break;}}}return dp[s.size()];}
};

2.18_多重背包

2.18.1 算法描述

多重背包和 0,1 背包非常相似,只不过多重背包中每个物品的个数是可以大于 1 的,但是 01 背包中物品的个数只能是 1 个

image-20211111163306376

如果将每个物品都展开转换成只有 1 个则是下面的情况:

image-20211111164056359

所以对于多重背包来说第一步是先展成 01 背包

2.18.2 C++ 代码实现

using namespace std;
#include<vector>
#include<iostream>
int test(){vector<int>weight = {1,3,4};vector<int>value={15,20,30};vector<int>nums = {2,3,2};int bagWeight = 10;// 将所有的物品都展开for(int i = 0;i<nums.size();i++){while(nums[i]>1){weight.push_back(weight[i]);value.push_back(value[i]);nums[i]--;}}vector<int>dp(bagWeight+1,0);dp[0]=0;for(int i=0;i<weight.size();i++){for(int j=bagWeight;j>=weight[i];j--){dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);}}return dp[bagWeight];
}
int main(){int res = test();cout<<res<<endl;return 0;
}

2.18.3 时空复杂度

时间复杂度:O(M * (N * K)) K 是 每个物品的数量

空间复杂度:O(M)

2.19_198 打家劫舍

打家劫舍的题会对每次更改哪个 dp[j] 有所讲究

2.19.1 算法描述

1.dp[i] 代表什么

在只有 i 间房屋的情况下,最后能偷到总金额 dp[i]

2.dp 的推导公式

分为偷与不偷两种情况

dp[j] = max(dp[j-1],dp[j-2]+nums[i])

这里一定要注意在偷与不偷的情况下应该选哪个下标

3.dp 初始化

根据推导公式所需进行初始化

在这里需要的是 dp[0] 和 dp[1] 分别代表偷第 0 个房子和偷第一个房子得到的最大利润

易错点:偷房子 1 时的最大利润还要进行判断

dp[0] = nums[0] 偷 0 的时候一定是偷 0 最大

dp[1] = max(nums[0],nums[1]) 偷 1 的时候需要判断哪个更大

4.确定遍历顺序

后面的结果是由前面结果推导出来的,所以从前向后遍历

5.举例 dp 数组

2.20.2 C++ 代码实现

class Solution {
public:int rob(vector<int>& nums) {if(nums.size()==0) return 0;if(nums.size()==1) return nums[0];vector<int> dp(nums.size(),0);dp[0] = nums[0];dp[1] = max(nums[0],nums[1]);for(int i = 2;i<nums.size();i++){dp[i] = max(dp[i-1],dp[i-2]+nums[i]); // 不偷,偷}return dp[nums.size()-1];}
};

2.20.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N)

2.21_213打家劫舍2

2.21.1 算法描述

这个题被当做模板是因为我们可以从任意的一个位置判断,判断任意的长度,所以这里设了一个 start 一个 end

本题就是去掉了开头第一个元素和结尾的元素分别判断两个子序列的结果,最后求最大值,如果将该模板套到上个题中 start 就变成了 0 ,end 就是 size-1:

去掉尾元素:

image-20211111190002201

去掉首元素:

image-20211111190017058

2.21.2 C++ 代码实现

class Solution {
public:int base(vector<int>nums,int start,int end){ // 左闭右闭if(end==start) return nums[start]; vector<int> dp(nums.size()); // 依旧保持原先的长度dp[start] = nums[start];dp[start+1] = max(nums[start],nums[start+1]);for(int i= start+2;i<=end;i++){// 偷 or 不偷dp[i] = max(dp[i-2]+nums[i],dp[i-1]);}return dp[end];}int rob(vector<int>& nums) {if (nums.size() == 0) return 0;if (nums.size() == 1) return nums[0];int a = base(nums,0,nums.size()-2);int b = base(nums,1,nums.size()-1);return max(a,b);  }
};

易错点:

①在 Base 方法中传入的为 start 和 end ,所以所有的一切都按照 start 和 end 来。下标之类的也要和 start 和 end 有关

②在最后 dp return 的时候

2.337_打家劫舍3

树+DP

2.22.1 算法描述

本题需要树的遍历。

1.dp [i] 代表什么

因为使用的树结构,树结构是链式存储的所以很难预判需要多大的存储空间,所以使用两个值进行记录偷与不偷获取的最大现金,然后再向上返回。

[0]:当前节点不偷的情况下资金的最大值;

[1]:当前节点偷的情况下资金的最大值;

2.dp 如何推导

这个节点分为不偷和偷两种情况,我们不管偷不偷,只管记录他的值,选择相应的下标就好

不偷:左右两边的子树就都可以偷

易错点:左右两边的孩子怎么偷还要再判断

int val1 = max(left[0],left[1])+max(right[0],right[1])

偷:左右子树就不能偷

int val2 = cur->val+left[0]+right[0];

3.dp 的初始化

如果这个树中一个节点都没有就将其设置为 0

dp[0]=0;dp[1]=0

4.确定遍历顺序

应该选择:后序遍历

后序遍历先遍历根节点,而根节点上的点比较多,不容易引发报警

5.举例 dp 数组

2.22.2 C++ 代码实现

class Solution {
public:// 长度为2的数组,0:不偷,1:偷vector<int> base(TreeNode* cur) {if (cur == NULL) return vector<int>{0, 0};vector<int> left = base(cur->left);vector<int> right = base(cur->right);// 偷cur : 左右两个孩子都不偷int val1 = cur->val + left[0] + right[0];// 不偷cur : 左孩子怎么偷还要进一步判断int val2 = max(left[0], left[1]) + max(right[0], right[1]);return {val2, val1};}int rob(TreeNode* root) {vector<int> res = base(root);return max(res[0], res[1]);}
};

2.22.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(logN)

2.23_121 买卖股票的最佳时机

2.23.1算法描述

1.dp[i] [j] 代表什么

这里一共有三种状态

dp[i] [0] 在这一天以及之前的所有天不进行任何操作所得的最多现金

dp[i] [1]:第 i 天持有股票所得的最多现金

dp[i] [2]:第 i 天不持有股票所得的最多现金

2.确定递推公式

因为是有限次数的买入卖出

持有股票分为两种情况:

昨天持有股票 or 今天买入

max(dp[i] [0]-prices[i],dp[i] [1])

不持有股票分为两种情况:

昨天是第一次持有股票阶段,今天卖出去+继续不持有股票

max(dp[i-1] [1]+prices[i],dp[i-1] [2])

dp[i] [0] 这里省略了,要不不进行任何操作本来是: 

很显然下面的公式总是 dp[i-1] [2] 大。

dp[i][0] = max(dp[i-1][0],dp[i-1][2]); 

继续向下推 dp[i] [1] 的式子也可以化简

dp[i][1] = max(dp[i-1][2]-prices[i],dp[i-1][1])

3.dp 初始化

根据上面的推导重视初始化第 0 天

dp[i] [0] :0 因为在这一天之前什么操作都没有进行所以也没赚也没赔,所以第 i 天的 [0] 全部为 0

dp[0] [1] = dp[0] [0]-prices[i]

dp[0] [2] = dp[0] [1]+prices[i]

4.遍历顺序

从前向后遍历

5.举例遍历 dp 数组

image-20211112223625911

2.23.2 C++ 代码实现

class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>>dp(prices.size(),vector<int>(3,0));dp[0][1] = -prices[0];dp[0][2] = 0;for(int i =1;i<prices.size();i++){dp[i][1] = max(dp[i-1][0]-prices[i],dp[i-1][1]);dp[i][2] = max(dp[i-1][1]+prices[i],dp[i-1][2]);}return dp[prices.size()-1][2];}
};

2.23.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N)

2.25_123买卖股票的最佳时机3

2.25.1 算法描述

1.dp[i] [j] 代表什么

dp[i] [0] :没有操作

dp[i] [1] :第一次持有:

①延续前一天持有的操作

dp[i-1] [1]

②第 i 天第一次买入股票

dp[i-1] [0]-price[i]

dp[i] [2] :第一次不持有:

①延续前一天操作

dp[i-1] [2]

②第 i 天卖出股票,是在第一天买入股票的基础上卖出

dp[i-1] [1]+price[i]

max(dp[i-1] [2],dp[i-1] [1]+price[i])

dp[i] [3] :第二次持有

同上

dp[i] [4] :第二次不持有

同上

3.dp 数组初始化

看一下在递推公式中最初的依赖:

第 0 天什么也不操作:dp[0] [0] = 0 在这之前没有进行任何操作

第 0 天进行第一次买入操作: dp[0] [1] = -prices[i]

第 0 天进行第一次卖出:dp[0] [2] = 0 当天买当天卖的利润是 0

第 0 天进行第二次买入:dp[0] [3] = -prices[0]

第 0 天进行第二次卖出:dp[0] [4] = 0

4.举例 dp 数组

image-20211113153821061

2.25.2 C++ 代码实现

class Solution {
public:int maxProfit(vector<int>& prices) {// 1. dp 定义vector<vector<int>>dp(prices.size(),(vector<int>(5,0)));// 2. dp 初始化dp[0][0] = 0;dp[0][1] = -prices[0];dp[0][2] = 0;dp[0][3] = -prices[0];dp[0][4] = 0;for(int i =1;i<prices.size();i++){dp[i][0] = dp[i-1][0]; // 不进行任何操作dp[i][1] = max(dp[i-1][0]-prices[i],dp[i-1][1]);dp[i][2] = max(dp[i-1][1]+prices[i],dp[i-1][2]);dp[i][3] = max(dp[i-1][2]-prices[i],dp[i-1][3]);dp[i][4] = max(dp[i-1][3]+prices[i],dp[i-1][4]);}return dp[prices.size()-1][4];}
};

2.25.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N * 5)

2.26_Base:188买卖股票的最佳时机4

2.26.1 算法描述

0:躺平,不进行任何操作

1k :持有–>持续前一天持有 or 第 k 天买入

2k:不持有–>持续前一天不持有 or 第 k 天卖出

为什么不对 dp[i] [0] 进行单独的赋值操作:

在第一次卖出股票之后(以操作两次为例):

dp[i] [0] = dp[i] [2] 就是那一天刚刚卖出股票,因为这一天任何操作都不能做,所以这个值就是一直不变的。在后面求 dp[i] [1] 时 ,持有状态可能从躺平状态进入也可能从不持有状态进入,也可能从前一次的持有状态进入。但是 dp[i] [0] 已经和不持有相等了,所以这里只用判断不持有就好了

这个题的递推公式可以借鉴进行两次操作的公式

2.26.2 C++ 代码实现

class Solution {public:int maxBase(vector<int>& prices,int k){vector<vector<int>>dp(prices.size(),vector<int>(2*k+1,0));for(int i =1;i<2*k;i+=2){if(i%2!=0) dp[0][i] = -prices[0];}for(int i =1;i<prices.size();i++){for(int j = 1;j<2*k+1;j = j+2){// 持有dp[i][j] = max(dp[i-1][j-1]-prices[i],dp[i-1][j]);// 不持有dp[i][j+1] = max(dp[i-1][j]+prices[i],dp[i-1][j+1]);}}return dp[prices.size()-1][2*k];}int maxProfit(int k, vector<int>& prices) {if(prices.size()==0) return 0;int maxVal = maxBase(prices,k);return maxVal;}
};

2.26.3 时空复杂度

时间复杂度:O(k*N)

空间复杂度:O(k*N)

2.27_309买卖股票的最佳时机含冷冻期

2.27.1 算法描述

因为包含冷冻期。冷冻期后的一天不能进行任何操作,又细分为以下几种状态

1.dp[i] [j] 代表什么

冷却期很像单次买入时的 dp[0] [0] 状态,是无关乎任何持有或者不持有的状态,需要单独列出

冷却期的值一定是从 不持有且当天卖出的情况中分离出来的,所以不持有且当天卖出这种情况要单独列出

持有的话可以从冷冻期持有也可以从持续持有中持有也可以当天买入持有,那么就要从这三种状态中求最大值

2.递推公式

  • 持有股票状态

这里要将买入和持有(但不买入)状态分开

1.冷冻期买入 1

2.从不持有买入 2

3:持有但是不买入 3

**状态合并 为 1:**1,2,3 可以进行合并 ,因为冷冻期并没有用到持有的状态

  • 不持有股票

1:当天卖出 2

2.持续前一天的持有状态或者冷冻期 3

状态合并:状态不能合并,因为 “当天卖出”和冷冻期有关

  • 冷冻期 4

3.dp 数组初始化

dp[0] [0] = 0 // 躺平

dp[0] [1] = -prices[i] // 持有股票:持续持有状态 or 当天买入

dp[0] [2] = 0 // 不持有股票(不包含当天买入)

dp[0] [3] = 0 // 当天卖出股票

dp[0] [4] = 0 // 冷冻期

最后结果是 :不持有(两种情况)+冷却期都有可能出现最大值

2.27.2 C++ 代码实现

class Solution {
public:int maxProfit(vector<int>& prices) {vector <vector<int>>dp(prices.size(),vector<int>(5,0));// 持有 or 不持有dp[0][0] = 0; // 躺平dp[0][1] = -prices[0];dp[0][2] = 0; // 卖出dp[0][3] = 0; // 不持有且不是当天卖出dp[0][4] = 0; // 冷冻期for(int i =1;i<prices.size();i++){// 持有dp[i][1] = max(dp[i-1][1],max(dp[i-1][3]-prices[i],dp[i-1][3]-prices[i]));//  当天卖出dp[i][2] = dp[i-1][1]+prices[i];// 仅不持有dp[i][3] = max(dp[i-1][2],dp[i-1][3]); // 不持有可以从两种状态过来// 冷冻期dp[i][4] = dp[i][2];}return max(dp[prices.size()-1][2],max(dp[prices.size()-1][3],dp[prices.size()-1][4]));
}
};

2.27.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N)

2.24_122买卖股票的最佳时机2(调整顺序)

2.24.1 算法描述

这个题和上面题的区别在于:

因为可以多次买卖,所以“持有”的状态可以从 “不进行任何操作 [i-1] [0]”,“之前就持有 [i-1] [1]”,“不持有[i-1] [2]” 三个地方来。

而不持有状态从 “之前就不持有 [i-1] [2]”和 “持有并卖出 [i-1] [1]+prices[i]” 两个状态来

本题的推导公式:

dp[i][1] = max(max(dp[i-1][2]-prices[i],dp[i-1][0]-prices[i]),dp[i-1][1]); // 持有状态从三个方面来
dp[i][2] = max(dp[i-1][2],dp[i-1][1]+prices[i]); // 不持有状态从两个方向来

上个题的推导公式:

dp[i][1] = max(dp[i-1][0]-prices[i],dp[i-1][1]);
dp[i][2] = max(dp[i-1][1]+prices[i],dp[i-1][2]);

2.24.2 C++ 代码实现

class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(),vector<int>(3,0));dp[0][0] = 0; // 0 不进行任何操作dp[0][1] = -prices[0]; // 1 持有dp[0][2] = 0; // 2 不持有for(int i = 1;i<prices.size();i++){dp[i][1] = max(max(dp[i-1][2]-prices[i],dp[i-1][0]-prices[i]),dp[i-1][1]); // 持有状态从三个方面来dp[i][2] = max(dp[i-1][2],dp[i-1][1]+prices[i]); // 不持有状态从两个方向来}return dp[prices.size()-1][2];}
};

2.24.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N)

2.28_714买卖股票的最佳时机含手续费

2.28.1 算法描述

一共分为三种状态:

躺平0,持有1,不持有2

1.持有股票:

max(dp[i-1] [1],dp[i-1] [2]-prices[i])

前一天就持有股票;当天买入

2.不持有股票

这里因为在成交之后要交费用问题,所以要将不持有分为:延续前一天不持有和当天卖出

延续前一天不持有股票:

dp[i] [2] = dp[i-1] [2]

当天卖出:

dp[i] [3] = dp[i-1] [1]+prices[i]-fee

因为上面两者可以算作不持有,所以即其合并

dp[i] [2] = max(dp[dp[i-1] [2],dp[i-1] [1]+prices[i]-fee])

3.初始化 dp 数组

因为第 0 天没有前一天,所以这一天只能买入或者不进行任何操作

dp[0] [0] = -prices[0];

dp[0] [1] = 0;

2.28.2 C++ 代码实现

class Solution {public:int maxProfit(vector<int>& prices, int fee) {vector<vector<int>> dp(prices.size(),vector<int>(3,0));dp[0][0] = 0; // 躺平dp[0][1] = -prices[0]; // 持有dp[0][2] =  0;// 当天卖出for(int i =1;i<prices.size();i++){// 持续前一天持有dp[i][1] = max(dp[i-1][1],dp[i-1][2]-prices[i]);// 什么时候卖出dp[i][2] = max(dp[i-1][2],dp[i-1][1]+prices[i]-fee); // 这里的状态可以进行合并}return dp[prices.size()-1][2];}
};

2.28.3 时空复杂度

时间复杂度:O(N)

空间复杂度 :O(N)

2.29_300最长上升递增子序列

LeetCode 题目链接

2.29.1 算法描述

1.暴力解

S1:6 和 7 比,7 选,然后进入一层 for 循环;与此同时 7 不选也进入一个 for 循环。0,1 不选,9 选与不选都又分别进入两个 for 循环。就是相当于后面的每个数选或者不选都会进入两个不同的 for 循环。就像回溯一样,有所少个数就会有多少个 for 循环

2.DP 解

image-20220117090623922

如:[6,7,0,1,9,3,5,8,4],这里从 7 开始

S1:7 和 6 比较,7>6 ,所以 dp[1] = dp[0]+1

S2:0 和 6 比较 ,0<6 ,所以不能选 6 作为 0 的最长递增子序列。0 和 7 比较,0<7 同理不能选为 0 的最长递增子序列;最后 0 的最长递增子序列还是 1

S3:1 和 6,7 比较,最长递增子序列都不变。1 和 0 比较,最长递增子序列 +1

。。。。。

S4:9 和 6 比较,9>6 dp[4] = dp[j]+1

(1)什么时候公式取 dp[i] :

就拿 9 举例,当其判断 0 时发现 ,0 的下标是 1 ,但是这时因为前面有 6,7 所以 9 的下标就已经是 3 了,所以 9 还是要去取 dp[i]

image-20220117092021142

(2)为什么使用两个 for 循环:

image-20211117170006491

因为是不连续的递增序列,有点类似于暴力搜索。以 7 为例,需要判断前面所有的递增数哪种组合可以达到最大值。

(3)DP 五部曲

1dp[i] 代表什么

dp[i] 表示,当总序列长度为 i 时最长子序列的长度

2.dp[i] 的递推公式

if(nums[i]>nums[j]) dp[i] = max(dp[i],dp[j]+1);

注意这里不是要 dp[i] 与 dp[j]+1 比较,而是取 dp[j]+1 的最大值

3.dp[i] 的初始化

dp[0] = 0

第 0 个数的最长上升子序列是他自己

4.dp[i] 的遍历顺序

从前向后遍历
5.举例 dp 数组

image-20211117205032728

2.29.2 C++ 代码实现

class Solution {
public:int lengthOfLIS(vector<int>& nums) {vector<int>dp(nums.size(),1);int result=1; // 最长子序列不一定发生在最后一个元素上,所以要对这个值进行保存for(int i =1;i<nums.size();i++){ // 初始值从 1 开始for(int j=0;j<i;j++){ // 和 i 之前的元素比较if(nums[i]>nums[j]) dp[i] = max(dp[i],dp[j]+1);}if(dp[i]>result) result = dp[i];}return result;}
};

2.29.3 时空复杂度

时间复杂度:O(N^2)

空间复杂度:O(N)

2.30_674最长连续递增序列

2.31.1 算法描述

这一题和上一题的区别在于这里不可以对原序列进行删减,所以对于 dp[i] 的判断是通过 i-1 得到的。而上一题的 dp[i] 是通过 之前所有的值得到的,这里只用一个 for 循环即可

1.dp[i] 代表什么

dp[i] 代表以下标 i 为结尾的数组的连续递增子序列的长度为 dp[i]

2.dp[i] 的递推公式

关键:当前 i 的状态由谁决定

关键:当前 i 的状态由他的前一个数决定

关键:是否需要连续

3.dp[i] 的初始化

同理,这里对所有的值都设为 1

4.dp 的遍历顺序

从前向后遍历

5.举例dp 数组

image-20211115155528247

2.31.2 C++ 代码实现

class Solution {
public:int findLengthOfLCIS(vector<int>& nums) {vector<int>dp(nums.size(),1); // 最小的递增序列是他自己dp[0] = 1; // 只有一个元素时递增序列的个数为 1int maxVal = 1;for(int i =1;i<nums.size();i++){if(nums[i]>nums[i-1]) dp[i] = dp[i-1]+1; // 在前面的结果 +1else dp[i] = 1; // 从头开始计算maxVal = max(dp[i],maxVal); // 更新最大值}return maxVal;}
};

2.31.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N)

2.32_718最长重复子数组

LeetCode 题目连接

2.32.1 算法描述

1.dp[i] [j] 代表什么

**因为这里有两个数组,所以需要定义二维 dp **

dp[i] [j] 代表以下标 i-1 为结尾的 A ,和以下标 j-1 为结尾的 B ,最长重复子数组的长度为 dp[i] [j](但是在 i-1,j-1 不相等时他们的值是 0 )

2.确定递推公式

当 A[i-1] 和 B[j-1] 相等的时候,因为需要连续所以要看前面的那个字符是否一样,如果一样 +1 。

如果 A[i-1] B[j-1] 不相等,则直接设为 0

dp[i] [j] = dp[i-1] [j-1]+1

很关键:当前值依赖于左斜上方的值,因为斜上方的值才是两个重复的值进行比较

3.dp 数组如何初始化

当 i,j 从 1 开始时,dp[i] [0] 和 dp[0] [j] 没有意义,这里初始化为 0

在后面 A[0] 和 B[0] 相等了, dp[1] [1] =dp[0] [0]+1 = 1

为什么 dp 的长度定义为 size+1 ,因为 A[0] B[0] 两个元素的值还是需要单独判断的,就相当于加了一个 dummy 。让这两个值在 for 循环中判断了

4.确定遍历顺序

最外层遍历 A 内层遍历 B ,也可以反过来

5.举例 dp 数组

image-20211115164252634

从这个图中可以看如果 A[i-1]==B[j-1] 那个这个值依赖于其左斜上方的值。这样就可以组成一个子序列

2.32.2 C++ 代码实现

class Solution {
public:int findLength(vector<int>& nums1, vector<int>& nums2) {vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));int result=0;for(int i=1;i<=nums1.size();i++){for(int j = 1;j<=nums2.size();j++){if(nums1[i-1]==nums2[j-1]){dp[i][j] = dp[i-1][j-1]+1;} if(result<dp[i][j]) result=dp[i][j];}}return result;}
};

2.32.3 时空复杂度

时间复杂度:O(N*M) 分别代表 A,B 的长度

空间复杂度:O(N*M)

2.33_1143最长公共子序列

2.33.1 算法描述

1.dp[i] [j] 代表什么

dp[i] [j] 代表:字符串 text1 和 字符串 text2 对应的下标为 [i-1] 和 [j-1] 时最长公共子序列为 dp[i] [j]

2.dp 递推公式

因为 A 是可以删减的,所以判断 [i-1] [j-1] 时这个值可以从

i-1与j-1 相同(上个题):

if(text1[i-1]==text[j-1]) dp[i][j] = dp[i-1][j-1]+1

i-1与j-1 不同:

就可以对 A 进行删除

image-20211116153031186
max(dp[i-1][j],dp[i][j-1])

**这个题和上一个题最大的不同就是在于 [i-1] 和 [j-1] 不相同的时候: **

因为 A 可以删减,所以 B[j-2] 的判断结果也可以引用到 B[j-1] 中,但是上一个题是不能删减的

所以这个题 [i] [j] 的结果都可以连续上个值的结果

3.dp 的初始化

dp[0] [0] 是没有意义的位置,所以设为0

4.dp 的遍历顺序

从前向后遍历

5.举例推导 dp 数组

image-20211116153516345

这里我以为原字符串是可以删的,匹配字符串是不可以删的。但是通过测试用例

[a,b,c,d,e] & [a,c,f] 发现两个都是可以删的,因为我们只需要求匹配的长度。如果匹配字符 [a,c,f] 不能删,那么最后的结果就是 0 ,画出来的 DP 数组就如下图所示

image-20220118101648085

如果短(匹配字符串可以删)

image-20220118102029611

2.33.2 C++ 代码实现

class Solution {
public:int longestCommonSubsequence(string text1, string text2) {vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));dp[0][0] = 0;for(int i = 1;i<=text1.size();i++){for(int j=1;j<=text2.size();j++){if(text1[i-1]==text2[j-1]) dp[i][j] = dp[i-1][j-1]+1; // 相同else dp[i][j] = max(dp[i][j-1],dp[i-1][j]); // 不相同}   }return dp[text1.size()][text2.size()];}
};

2.33.3 时空复杂度

时间复杂度:O(N*M)

空间复杂度:O(N*M)

2.34_1035不相交的线

LeetCode 题目链接

2.34.1 算法描述

以 nums1 和 nums2 两个数组为例:

nums1 = [2,5,1,2,5],

nums2 = [10,5,2,1,5,2]

2 和第一个出现的 2 相连,5 和 2 后面的 5 相连,不可以和前面的 5 连。。。。。

如果想让两个数组生成的结果不想交,则必须保证: 子序列有序

这个题就是寻找相同子序列

2.34.2 C++ 代码实现

class Solution {
public:int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));dp[0][0] = 0;for(int i = 1;i<=nums1.size();i++){for(int j=1;j<=nums2.size();j++){if(nums1[i-1]==nums2[j-1]) dp[i][j] = dp[i-1][j-1]+1; // 相同else dp[i][j] = max(dp[i][j-1],dp[i-1][j]); // 不相同}   }return dp[nums1.size()][nums2.size()];}
};

2.34.3 时空复杂度

时间复杂度:O(M*N)

空间复杂度:O(M*N)

2.35_53最大子序和

2.35.2 算法描述

1.dp[i] 是什么

dp[i] 保存的是当前元素所组成的最大子序的最大子序和。maxValue 保存的是整个序列的最大子序和

2.dp[i] 的递推公式

当前值是依赖于前面值的,递推公式分两种情况:

①将这个值加入最大子序列和

②以这个子序列为起点

max(dp[i-1]+nums[i],nums[i])

以上的两个操作都能保证最后的结果是连续的子序列

2.35.2 C++ 代码实现

class Solution {
public:int maxSubArray(vector<int>& nums) {vector<int>dp(nums.size(),0);dp[0] = nums[0];int maxVal = nums[0];for(int i =1;i<nums.size();i++){dp[i] = max(dp[i-1]+nums[i],nums[i]);maxVal = max(maxVal,dp[i]);}return maxVal;}
};

易错点:

如果不把这个值设置为最大子和中的一个数,那么 nums[i] 就是起点。因为这是一个连续子和,所以不能连续之前的那个值,即 dp[i] = max(dp[i-1],dp[i-1]+nums[i]) 这样写就会断开

2.35.i3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(N)

2.36_392判断子序列

2.36.1 算法描述

这个题和 1143 其实是异曲同工,两个题一个要求返回 bool 类型,一个返回 int 类型。AB 都可以删除。因为题目中已经说明了 s 是子序列,所以最后只要判断最长子序列的个数是不是 s 就好

匹配题目看似是匹配返回的值是 bool 类型,但是 bool 类型的值如果放在 dp 中信息量太少,所以 dp 中一般还是放数字

本题 A 序列的值要全部满足 B 序列

A: a b c

B : a h b g d c

a-a 相等,dp 对应的下标 +1

b-b 相等,是在 a-a 相等的基础上 +1

a-h 不相等,dp 的值为 a-a

b-a 不相等,a-a 的值是不能给 b-a 的,因为这里要 a b c 全部满足才行,b-a 不满足

2.36.2 C++ 代码实现

class Solution {
public:bool isSubsequence(string s, string t) {vector<vector<int>>dp(s.size()+1,vector<int>(t.size()+1,0));for(int i =1;i<=s.size();i++){for(int j = 1;j<=t.size();j++){if(s[i-1]==t[j-1]) dp[i][j] = dp[i-1][j-1]+1;else dp[i][j] = dp[i][j-1];}}if(dp[s.size()][t.size()]==s.size()) return true;return false;}
};

3.36.3 时空复杂度

时间复杂度:O()

总结:

子序列问题

A:a b c d e

B:a c e

第一行 a-a 相等+1

在第二行 b-a 不相等,但是 a-a 可以传给 b-a 所以他的值可以来自上方

与此同时第二行 b-a 不相等但是 b-a 的值还是可以算的,所以他的值可以来自左侧,并选择其中一个大的值

image-20211116213143315

392

A: a b c

B : a h b g d c

因为这个题只有 A 全部满足 B 时才能算进行计数。也就是说在第一行 a-a 虽然满足了,但是在第二行 b-a 不满足,也不能算满足,所以 a-a 的值不能传给 b-a

image-20211116212323168

718

A:1 2 3 2 1

B:3 2 1 4 7

必须要连续满足,所以只能对连续对角线的值进行相加

image-20211116212607317

要分别看:

A B 子序列中的字母 α ,β

① α 和 β 相等时这个他俩对应的下标依赖谁

②α 和 β 不相等时他俩对应的下标依赖谁

2.37_115不同的子序列

2.37.1 算法描述

1.dp[i] [j] 代表什么

代表题目就完事

代表长度为 [i-1] 的 t 和 长度为 [j-1] 的s 所匹配的种类个数

这个子序列问题求的是 “匹配”不是 “长度”

2.dp 的递推公式

同样分成 t[i-1] 等于或者不等于 s[j-1] 两种情况

(1)t[i-1] 与 j[j-1] 相等分为两个部分:

为什么还分为不用来匹配的情况:

s:bagg

t:bag

这里一共有两个 g ,所以用哪个 g 去匹配 bag 都是可以的

所以相等时的最终结果是两个值相加

①s[j-1] 用来匹配 dp[i-1] [j-1]

②s[j-1] 不用来匹配 dp[i-1] [j] ,也就是这俩值不相等

(2)t[i-1] 和 s[j-1] 不相等

因为 s 是可以删除的,所以当 s = bae 的结果是 s=baeg 的结果的子集。也就是说 baeg 的结果可以借鉴 bae 的结果。那么在不相等的时候 baeg 的结果来自 bag

不相等就没有办法匹配,所以直接返回 dp[i] [j-1]

3.dp 数组初始化

首先说 dp[i] [0]

这里的 s 是可以进行删除的,以 bageg 匹配 bag 为例,为什么最后能够匹配到两个,是因为将 bageg 第一个 g 和第一个 e 删除变为了 bag 以及将 bageg 的最后两个 eg 删除变为了 bag ,这时候 s 和 t 就能有两次匹配。

这里我们的 dp 是以 0 开始的,0 就代表没有字符。

当 s=baged 时,s 也可以充当 0 ,因为 s 是可以随时删除字符的。所以 dp[i] [0] 一定为 1

其次是 dp[0] [j]

因为 t 不是子序列是要进行完全匹配的字符串,所以 t 是不能删除,所以 dp[0] [j] 全为 0

4.确定遍历顺序

从上到下,从左到右

5.举例 dp 数组

IMG_ED0470EF8B3C-1

2.37.2 代码实现

class Solution {public:int numDistinct(string s, string t) {vector<vector<uint64_t>>dp(t.size()+1,vector<uint64_t>(s.size()+1,0)); // 必须定义为 unit_64// 初始化for(int i = 0;i<s.size()+1;i++){dp[0][i] = 1;}for(int i=1;i<=t.size();i++){	 // 因为 t 是子串,所以 t 要放在 i 的位置for(int j= 1;j<=s.size();j++){if(t[i-1]==s[j-1]) dp[i][j] = dp[i-1][j-1]+dp[i][j-1]; // 不用 j 匹配和用 j 匹配else dp[i][j] = dp[i][j-1]; // 匹配问题子串不能删减}}return dp[t.size()][s.size()];}
};

2.37.3 时空复杂度

2.38_583两个字符串的删除操作

2.38.1 算法描述

1.dp[i] [j] 代表什么

dp[i] [j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数

2.递推公式

当word1[i - 1] 与 word2[j - 1]相同:

dp[i] [j] = dp[i - 1] [j - 1]:不用删除任何元素,直接等于斜对角线

当word1[i - 1] 与 word2[j - 1]不相同:

删除 word1 中的元素,也就是行向上退一个 dp[i - 1] [j] + 1;

删除 word2 中的元素,也就是列向左退一个 dp[i] [j-1] + 1;

同时删除:dp[i - 1] [j - 1] + 2

因为问的是最小值:

dp[i] [j] = min({dp[i - 1] [j - 1] + 2, dp[i - 1] [j] + 1, dp[i] [j - 1] + 1});

3.dp 初始化

这里在进行初始化时需要注意,通过画表可知 ,这里的 x 必须当成空字符看。所以在进行初始化时第一行和第一列是顺序进行初始化的

4.dp 遍历顺序

从左到右,从上到下

5.举例 dp 数组

image-20211214173235714

2.38.2 C++ 代码实现

class Solution {
public:int minDistance(string word1, string word2) {vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));// 初始化for(int i=0;i<=word1.size();i++) dp[i][0]=i;for(int j=0;j<=word2.size();j++) dp[0][j]=j;for(int i=1;i<=word1.size();i++){for(int j=1;j<=word2.size();j++){if(word1[i-1]==word2[j-1]) dp[i][j] = dp[i-1][j-1]; // 两个字符串都不用删除元素else dp[i][j] = min({dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+2}); // 需要某一个字符串删除一个元素或者二者都删}}return dp[word1.size()][word2.size()];}
};

2.38.3 C++ 时空复杂度

时间复杂度:O(M*N)

空间复杂度:O(M*N)

2.39_72编辑距离

2.39.1 算法描述

1.dp[i] [j] 代表什么

以下标 i-1 为结尾的字符串 word1 和以下标 j-1 为结尾的的字符串 word2 ,最近编辑距离为 dp[i] [j]

2.递推公式

一共分为两大种四小种情况:

word1[i-1] == word2[j-1] 相等

不进行任何操作,因为只有在不进行任何操作的情况下才能保证操作数最小

我们不需要判断要进行什么操作可以让结果最小,我们只需要将这些操作列出来让程序判断

删除元素:

  • 操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。

即:dp[i] [j] = dp[i - 1] [j] + 1;

  • 操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。

即 :dp[i] [j] = dp[i] [j-1] + 1;

添加元素:

假设说目前 word1 长度或者对应的位置还不对,要给 word1 添加一个元素使得 word1[i-1] 和 word2[j-1] 相同 。那还不如说给 word2 减掉一个元素

即 : dp[i] [j] = dp[i] [j-1] +1

替换元素:

替换的意思是说将 word1 的 word1[i-1] 进行替换让其与 word2[j-1] 相同。因为这两个值相同,所以看得是对角线元素。

那么只需要在对应 dp[i-1] [j-1] 的位置上 +1 即可。这一步的替换操作和上一题的删除有所不同,上一步的删除必须要两个元素都删才能保证两个串相等。但是这里的替换当两个字符都不相等时,只需要将其中一个进行更改字符就行,所以最后是 +1 的操作

即:dp[i] [j] = dp[i-1] [j-1]+1

最终结果:

最后只需要求上面三种操作那种操作可以使得操作数最少:

dp[i] [j] = min({dp[i-1] [j],dp[i] [j-1],dp[i-1] [j-1]})+1

3.dp 数组如何初始化

其实初始化通过画图和对题目的理解就可以得到结果

4.确定遍历顺序

从左到右,从上到下

5.举例dp数组

image-20211118212510113 image-20211118212455048

2.39.2 C++ 代码实现

class Solution {
public:int minDistance(string word1, string word2) {vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));// 初始化for(int i=0;i<word1.size()+1;i++) dp[i][0]=i;for(int j=0;j<word2.size()+1;j++) dp[0][j]=j;for(int i = 1;i<=word1.size();i++){for(int j=1;j<word2.size()+1;j++){if(word1[i-1]==word2[j-1]) dp[i][j] = dp[i-1][j-1];else dp[i][j] = min({dp[i-1][j],dp[i][j-1],dp[i-1][j-1]})+1;}}return dp[word1.size()][word2.size()];}
};

2.40_647 回文子串

2.40.1 算法描述

1.dp[i] [j]代表什么

表示在范围内 [i,j] 的子串是否是回文子串,bool 类型变量

2.dp 递推公式

在确定递推公式时,就要分析如下几种情况。

整体上是两种,就是s[i]与s[j]相等,s[i]与s[j]不相等这两种。

当s[i]与s[j]不相等,dp[i] [j]一定是false。

当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况:

  • 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串

  • 情况二:下标i 与 j相差为1,因为一共就两个元素,也是文子串

  • 情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间

3.dp 数组初始化

这个题 dp[i] [j] 就是 index 值,不需要 0 进行占位,所以每个值都要进行独立的判断,所以初始化可以先全部设置为 false

4.确定遍历顺序

这里要从后向前判断。比如 abc ,每次将 i 固定改变 j 的值,j 是往后走的。从后向前判断。

当 i 指向 c 的时候 j 为 c 后面的元素。

当 i 指向 b ,j 就要判断 bb,bc

假设说要判断 a-c 是否是回文串,首先要知道 b 是否是回文串,所以这里的动归要从后向判断

i 从后向前,j 从前向后

image-20211120164913655

5.举例 dp 数组

2.40.2 C++ 代码实现

class Solution {public:int countSubstrings(string s) {vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));int res = 0; // 记录 true 的个数for(int i=s.size()-1;i>=0;i--){for(int j = i;j<s.size();j++){ // i 固定不动 j 一直向后,判断 [i,j] 是否是回文串if(s[i]!=s[j]) dp[i][j] = false;else{if(j-i<=1){ // 情况1,2dp[i][j] = true;res++;}else if(dp[i+1][j-1]){ // 这里直接判断中间子串是否是回文dp[i][j] = true;res++;}}}}return res;}
};

2.40.3 时空复杂度

时间复杂度:O(N2)

空间复杂度:O(N2)

2.41_516最长回文子序列

2.41.1 算法描述

这个题和上一个题的不同在于这里可以删减,上个题不能删减

1.dp[i] [j] 代表什么

字符串 s 在 [i,j] 范围内最长的回文子序列的长度为 dp[i] [j]

2.确定递推公式

关键在于 : s[i] 和 s[j] 是否相相等

如果s[i]与s[j]相同,那么dp[i] [j] = dp[i + 1] [j - 1] + 2;

image-20211119133441931

如果不相同:

如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子串的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。

加入s[j]的回文子序列长度为dp[i + 1] [j]。

加入s[i]的回文子序列长度为dp[i] [j - 1]。

那么dp[i] [j]一定是取最大的,即:dp[i] [j] = max(dp[i + 1] [j], dp[i] [j - 1]);

3.dp 数组如何初始化

这个题和上面的题不一样,这里的 i 是从后向前判断,j 是在 i 的下一个位置判断,从前向后判断,也就是 s[i] 和 s[j] 是两个不一样的元素,所以必须要手动初始化

在 dp[i] [i] 时指向的元素是同一个,这时候下标为 1

4.确定遍历顺序

从递推公式可以看出:

dp[i] [j] = dp[i + 1] [j - 1] + 2;

dp[i] [j] = max(dp[i + 1] [j], dp[i] [j - 1]);

所以 i 是从后向前推, j 是从前向后推的

测试用例:bbbab

image-20211120155545502

ab;ba;b(a)b;bb;b(b)a;b(ba)b

5.举例 dp 数组

2.41.2 C++ 代码实现

class Solution {
public:int longestPalindromeSubseq(string s) {vector<vector<int>> dp(s.size(),vector<int>(s.size(),0));// 初始化for(int i=0;i<s.size();i++) dp[i][i] = 1;// 赋值for(int i = s.size()-1;i>=0;i--){for(int j =i+1;j<s.size();j++){if(s[i]==s[j]) dp[i][j] = dp[i+1][j-1]+2;else dp[i][j] = max(dp[i+1][j],dp[i][j-1]);}}return dp[0][s.size()-1];}
};

易错点:

①这里没有对 i=j 的时候进行单独判断

② j 是从 i+1 开始判断的。如果 j 从 i 开始判断则 [j-1] 就会越界

3.其他题目

3.1_91解码方法

LeetCode题目连接

3.1.1 算法描述

DP 有几种方法组成

这道题需要一个个的字符进行判断所以考虑回溯或者动态规划。但是这个题只需要求个数,不需要将每一个答案列举出来所以使用 DP

本道题一共有三种情况:

以 226 组成的结果为例,求 i = 6 能组成的个数

1.dp[i] 代表什么

dp[i] 代表当字符串中包含 0~i 个字符时字符串有几种不同的结果组成

2.dp[i] 的递推公式

下面以 i=6 为例

组成 dp[i] 一共有两种情况,6 单独看,26 一起看。以及这个位置能单独看也能一起看。转换成 i 后的描述如下

① i 位置单独看

dp[i] = dp[i-1]

22 有两种方法。226 后如果 6 单独看还是有 2 中方法那就是 BB6 和 V6

②i 和 其前面的数组合成一起看

dp[i] = dp[i-2]

那么数组就被分成 2,26 就是 2 的结果,BZ 是一种

③ i 既能单独看也能和前面合并

dp[i] = dp[i-1]+dp[i-2]

1+2 = 3

对于上面的描述其实还要多判断一步 i 是否可以单独看,是否需要和并看,那么加上字母只能在 1~26 的这个范围条件后 dp 递推公式就变成了

image-20211210171520447

3.dp 初始化

根据 dp 递推公式可以看出要算出 i-2 也就是需要对 0,1 进行初始化。因为本题需要对 i 的范围分成多种情况,所以 dp[1] 不是那么好初始化,所以需要添加一个哨兵节点。

前导 0 现象:与此同时为了让 s 的 index 和 dp 一一对应这里对 s 前面也添加一个空字符

dp[0] = 0

3.1.2 C++ 代码实现

class Solution {public:int numDecodings(string s) {int n = s.size();s = " " + s;vector<int> dp(n + 1,0);dp[0] = 1;        for(int i = 1; i < n + 1; i++) {int a = s[i] - '0'; // 单独值int b = (s[i - 1] - '0') * 10 + s[i] - '0'; // 合并值if(1 <= a && a <= 9) dp[i] = dp[i - 1]; // 单独看的个数if(10 <= b && b <= 26) dp[i] += dp[i - 2]; // 合并看的个数}return dp[n];}
};

3.1.3 时空复杂度

时间复杂度:O(n)

空间复杂度:O(n)

3.2_10正则表达式匹配

3.2.1 算法描述

1.dp[i] [j] 的含义

dp[i] [j] 含义是:p 的前 [j-1] 个字符能否匹配s的前 [i-1] 个字符

3.2

3.3_42接雨水

3.3.1 算法描述

1.暴力法

如果想求一个格子中的雨水公式为,这就是短板效应:

min(max(h[0i]),max(h[in-1]))-h[i]

可以理解为将 cur 两边的柱子移动到 cur 的位置,中间夹着的就是水柱的高度

image-20220122230332501

2.DP

对于上面的结果还有一个可以优化的地方。每次在判断 i 的最大左边界和 i 的最大由边界时都要从 i=0 或者 i = size-1 时进行逐个判断,判断 0~i 谁更大,但是这样会造成时间复杂度为 O(N²),所以这里使用 DP

1.dp[i] 代表什么

LeftMax[i]:从 0~i dp i 的最大值

rightMax[i] :从 size-1~i dp i 的最大值

2.dp 的递推公式

dp[i] = max(dp[i-1],hight[i]);

3.dp i 的初始化

从 0 开始初始化,从 size -1 开始初始化

4.迭代顺序

一个从前往后,一个从后往前

3.3.2 代码实现

class Solution {public:int trap(vector<int>& height) {int res = 0;int size = height.size();for(int i =0;i<size;i++){int maxLeft = 0,maxRight=0; // 找到左边的最大值和右边的最大值for(int j = i;j>=0;j--){maxLeft = max(maxLeft,height[j]); // 0~i 的最大值}for(int j = i;j<size;j++){maxRight = max(maxRight,height[j]);}// 求面积res+=min(maxLeft,maxRight)-height[i];}return res;}
};

2.DP

class Solution {public:int trap(vector<int>& height) {int res = 0;int size = height.size();if(size==0) return res;vector<int> leftMax(size),rightMax(size);leftMax[0] = height[0];rightMax[size-1] = height[size-1];// 初始化两个 dp dp i 代表指向 i 时左边的最大值和右边的最大值分别是多少for(int i = 1;i<size;i++){ // 从 1 开始leftMax[i] = max(height[i],leftMax[i-1]);}for(int i = size-2;i>=0;i--){rightMax[i] = max(height[i],rightMax[i+1]);}// 遍历 dp 求面积for(int i = 0;i<size;i++){res+=min(leftMax[i],rightMax[i])-height[i];}return res;}
};

3.3.3 时空复杂度

1.暴力:

时间:O(N²) ; 空间:O(1)

2.DP

时间:O(N) ; 空间:O(N)

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 产生临时对象的情况和性能优化

    目录产生临时对象的情况和解决方案1.以传值的方式给函数传递参数2.类型转换生成的临时对象/隐式类型转换以保证函数调用成功3.函数返回对象的时候产生临时对象的情况和解决方案 1.以传值的方式给函数传递参数 执行以下案例: 传值时的赋值操作 #include <iostream>usin…...

    2024/4/13 6:25:14
  2. SpringMVC 组件解析

    1.SpringMVC的执行流程 1.用户发送请求至前端控制器DispatcherServlet。 2.DispatcherServlet收到请求调用HandlerMapping处理器映射器。 3.处理器映射器找到具体的处理器&#xff08;可以根据xml配置、注解进行查找&#xff09;&#xff0c;生成处理器对象及处理器拦截器&am…...

    2024/4/13 6:25:24
  3. 在IntelliJ IDEA中如何使用Gradle

    概述 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具&#xff0c;该工具使用一种基于Groovy的特定领域语言(DSL)来声明项目设置&#xff0c;抛弃了基于XML的繁琐配置&#xff0c;当前支持的语言限于Java、Groovy、Kotlin和Scala&#xff1b;与Maven比…...

    2024/4/18 5:29:50
  4. 4.字符串

    1.字符串 2.LeetCode 相关题目 2.1_344反转字符串 LeetCode题目链接 2.1.1算法描述 使用双指针翻转字符串 可以与翻转链表进行对比学习 2.1.2 Python 代码实现 class Solution:def reverseString(self, s: List[str]) -> None:"""Do not return anyt…...

    2024/4/13 6:25:14
  5. 国经信中心「APEC文化+」:中国有谊酒——团结派“有谊义””忠义无双“发布2022新春祝福

    中国品牌网全球ChinaBrand品牌公益与ESG资讯&#xff1a; 「APEC文化」&#xff1a; 中国有谊酒——团结派“有谊义””忠义无双“发布2022新春祝福 版权猫ipMALL的签约艺术家CalvinCalbin卡鲁兵卡鲁兵蜀黍经过同意&#xff0c;将知名爱国者、勇于改变的世界洪门历史文化协会…...

    2024/4/13 6:24:59
  6. 7.树相关

    1.树 1.1树的定义 树是一种非线性数据结构&#xff0c;在某些时候非线性结构比线性结构处理数据时要快的多。 1.2 树的应用场景 1.2.1 Linux/Unix 文件夹与文件 1.2.2 继承 下图用树结构模拟的各类之间的继承关系 1.2.3 树的抽象数据类型 1.二叉树的定义 struct TreeNod…...

    2024/4/8 18:57:15
  7. AI - 什么是假设检验?

    1. 定义&#xff1a;假设检验(hypothesis testing)&#xff0c;又称统计假设检验&#xff0c;是用来判断样本与样本、样本与总体的差异是由抽样误差引起还是本质差别造成的统计推断方法。显著性检验是假设检验中最常用的一种方法&#xff0c;也是一种最基本的统计推断形式&…...

    2024/4/13 6:25:04
  8. 【C语言】操作符详解

    文章目录前言操作符分类• 算数操作符• 移位操作符• 位操作符• 赋值操作符• 单目操作符• 关系操作符• 逻辑操作符• 条件操作符• 逗号表达式• 下标引用• 函数调用• 结构成员总结前言 C语言中操作符不多&#xff0c;但是有些相同的操作符都是在不同的表达式中&#x…...

    2024/4/5 2:08:36
  9. 荣耀机试题—进制转换:十进制数转成任意进制数(2-64进制)

    本文不直接解答该题&#xff0c;只解决该题的核心部分&#xff0c;实现十进制数转换成任意进制数。 荣耀机试题的原题&#xff1a; 小明最近在学bash语言&#xff0c;发现Bash算术运算相比于C/pvthon等其他语言有一个特性&#xff0c;是其中常量表示规则: 1、一般形式是[base…...

    2024/5/9 13:19:08
  10. 6.栈和队列

    1.栈 Stack 1.1 栈的定义 栈提供push 和 pop 等等接口&#xff0c;所有元素必须符合先进后出规则&#xff0c;所以栈不提供走访功能&#xff0c;也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。 栈是以底层容器完成其所有的工作&#xff0…...

    2024/4/13 17:22:11
  11. leetcode 93. 复原 IP 地址 [回溯]

    1.题目要求 leetcode 93. 复原 IP 地址 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 ‘.’ 分隔。 例如&#xff1a;“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址&#x…...

    2024/4/14 1:29:54
  12. WP网站固定连接被更改后出现404–一步教你找回

    详细内容参考链接&#xff1a; WP网站固定连接被更改后出现404–一步教你找回 – Rookie diary (tuyogf.top)https://tuyogf.top/2022/02/11/wp%e7%bd%91%e7%ab%99%e5%9b%ba%e5%ae%9a%e8%bf%9e%e6%8e%a5%e8%a2%ab%e6%9b%b4%e6%94%b9%e5%90%8e%e5%87%ba%e7%8e%b0404-%e4%b8%80%…...

    2024/4/13 6:26:04
  13. spring session报错:ClassNotFoundException: org.springframework.session.SaveMode

    今天尝试着自己玩一下Spring session这个东西 我Spring的版本是&#xff1a; 2.1.5.RELEASE 1、加入了以下依赖&#xff1a; 2、配置文件&#xff1a; 3、启动类开启注解 4、启动。。。 居然报错了。。。百度又找不到&#xff0c;这是依赖不完整吗&#xff1f;导致没有sessio…...

    2024/4/20 9:35:48
  14. win10+python3.7如何配置搭建tensorflow

    目录 1. 安装TensorFlow 2. 验证是否安装成功 1. 安装TensorFlow 打开anaconda prompt&#xff0c;下载tf前先下载对应的Keras&#xff0c; 键入pip install keras2.3.1&#xff0c; 待下载成功后再键入pip install tensorflow2.2。 PS&#xff1a; &#xff08;1&#…...

    2024/4/13 6:26:19
  15. opencv c++ 贴图

    float32类型贴图&#xff1a; cv::Mat srcResizea;resize(src, srcResizea, cv::Size(dstWidth, dstHeight));cv::Mat dst cv::Mat::zeros(srcResizea.size(), CV_32FC3);srcResizea.convertTo(dst, CV_32FC3);Mat_L2_mormal(dst);int width 360;int width_crop dstWidth;cv…...

    2024/4/15 9:47:24
  16. mysql的进阶学习--基础篇--函数

    之前我们学习过SQL中的聚合函数, 这只是函数的一种. 接下来要继续学习什么是函数, 函数的应用场景, 以及mysql当中常见的函数有哪些. 什么是函数 函数: 是指一段可以直接被另一段程序调用的程序或者代码. 事实上, mysql内部设置了好多函数, 我们所要做的就是调用或者说是使用…...

    2024/4/15 15:32:14
  17. Lock wait timeout exceeded; try restarting transaction

    Lock wait timeout exceeded; try restarting transaction 1. 异常 java.lang.reflect.InvocationTargetException: nullat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)at sun.reflect.Del…...

    2024/4/13 6:25:59
  18. 人体组织平面波超声成像仿真(MATLAB k-Wave仿真)

    k-Wave仿真工具 http://www.k-wave.org/ k-Wave 是一个用于MATLAB和 C 的开源声学工具箱&#xff0c;专门为复杂和组织中的时域声学和超声仿真模拟而设计。 对人体组织进行超声成像仿真仿真 1.设置线阵超声换能器 定义128阵元线阵换能器用于超声成像。 2.设置背景介质密度…...

    2024/4/18 9:50:06
  19. uni-app背景图片全屏

    最近在学习写uni-app项目&#xff0c;记录一下。 最初设置好的背景图片总是显示不出来&#xff0c;后来发现最外层view要设置高度才可以使图片正确显示&#xff0c;可设height为100vh。...

    2024/4/13 9:17:45
  20. Python基础091:解决Pycharm的python console控制台命令提示符是In[2]而不是>>>的问题

    问题 解决Pycharm的python console控制台命令提示符是In[2]而不是>>>的问题&#xff0c;如下所示&#xff1a; 解决方法 在 Setting中 搜索 ipython &#xff0c;找到Console &#xff0c;单击Console&#xff0c;将【Use IPython if available】 的对勾去掉&…...

    2024/4/13 6:26:04

最新文章

  1. 【Java】从0实现一个基于SpringBoot的个人博客系统

    从0实现一个基于SpringBoot的个人博客系统 项目介绍准备工作数据准备创建项目准备前端页面编写配置文件 项目公共模块实体类公共层业务代码持久层实现博客列表实现博客列表约定前后端交互接口 实现博客详情约定前后端交互接口实现服务器代码 实现登录JWT令牌JWT令牌生成和校验实…...

    2024/5/9 13:35:24
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. k8s_入门_kubelet安装

    安装 在大致了解了一些k8s的基本概念之后&#xff0c;我们实际部署一个k8s集群&#xff0c;做进一步的了解 1. 裸机安装 采用三台机器&#xff0c;一台机器为Master&#xff08;控制面板组件&#xff09;两台机器为Node&#xff08;工作节点&#xff09; 机器的准备有两种方式…...

    2024/5/9 5:33:21
  4. Redis精品案例解析:Redis实现持久化主要有两种方式

    Redis实现持久化主要有两种方式&#xff1a;RDB&#xff08;Redis DataBase&#xff09;和AOF&#xff08;Append Only File&#xff09;。这两种方式各有优缺点&#xff0c;适用于不同的使用场景。 1. RDB持久化 RDB持久化是通过创建一个二进制的dump文件来保存当前Redis数据…...

    2024/5/7 16:47:17
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/8 6:01:22
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/7 9:45:25
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/9 4:20:59
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/8 20:48:49
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/8 19:33:07
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/8 20:38:49
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/9 7:32:17
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57