动态规划专题精讲1

news2024/9/23 13:25:40

致前行的人:

                要努力,但不要着急,繁花锦簇,硕果累累都需要过程!

前言:

        本篇文章为大家带来一种重要的算法题,就是动态规划类型相关的题目,动态规划类的题目在笔试和面试中是考察非常高频的一类,要想掌握这一类的题目就需要在掌握正确的学习方法的前提下做大量的练习,下面为大家介绍几道动态规划中比较经典的题目,每一道题目都有非常详细的思路实现,看完相信大家一定会有收获的!

1.动态规划概念:

动态规划,英⽂:Dynamic Programming,简称DP,如果某⼀问题有很多重叠⼦问题,使⽤动态规划是最有效的,动态规划中每⼀个状态⼀定是由上⼀个状态推导出来的

2.动态规划解题步骤

1. 确定dp数组(dp table)以及下标的含义
2. 确定递推公式
3. dp数组如何初始化
4. 确定遍历顺序
5. 举例推导dp数组

3.动态规划应该如何调试

找问题的最好⽅式就是把dp数组打印出来,看看究竟是不是按照⾃⼰思路推导的,做动规的题⽬,写代码之前⼀定要把状态转移在dp数组的上具体情况模拟⼀遍,⼼中有数,确定最后推出的是想要的结果。

然后再写代码,如果代码没通过就打印dp数组,看看是不是和⾃⼰预先推导的哪⾥不⼀样。如果打印出来和⾃⼰预先模拟推导是⼀样的,那么就是⾃⼰的递归公式、初始化或者遍历顺序有问题了。如果和⾃⼰预先模拟推导的不⼀样,那么就是代码实现细节有问题。

4.动态规划经典题目

斐波那契数 oj链接

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。

示例 1:

输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:

输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:

输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

思路:

按照动规五部曲依次推导

这⾥我们要⽤⼀个⼀维dp数组来保存递归的结果
1. 确定dp数组以及下标的含义
dp[i]的定义为:第i个数的斐波那契数值是dp[i]
2. 确定递推公式
为什么这是⼀道⾮常简单的⼊门题⽬呢?
因为题⽬已经把递推公式直接给我们了:状态转移⽅程
dp[i] = dp[i - 1] + dp[i - 2];
3. dp数组如何初始化
题⽬中把如何初始化也直接给我们了,如下:

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


4. 确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍
历的顺序⼀定是从前到后遍历的
5. 举例推导dp数组
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导⼀下,当N为10的时候,dp数组应
该是如下的数列:
0 1 1 2 3 5 8 13 21 34 55
如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是⼀致的。
以上我们⽤动规的⽅法分析完了,C++代码如下:

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

时间复杂度:O(n)
空间复杂度:O(n)

爬楼梯oj链接

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

思路:

爬到第⼀层楼梯有⼀种⽅法,爬到⼆层楼梯有两种⽅法。那么第⼀层楼梯再跨两步就到第三层 ,第⼆层楼梯再跨⼀步就到第三层。
所以到第三层楼梯的状态可以由第⼆层楼梯 和 到第⼀层楼梯状态推导出来,那么就可以想到动态规划了。

动规五部曲:
定义⼀个⼀维数组来记录不同楼层的状态
1. 确定dp数组以及下标的含义
dp[i]: 爬到第i层楼梯,有dp[i]种⽅法
2. 确定递推公式
如果可以推出dp[i]呢?
从dp[i]的定义可以看出,dp[i] 可以有两个⽅向推出来。
⾸先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种⽅法,那么再⼀步跳⼀个台阶不就是dp[i]了么。
还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种⽅法,那么再⼀步跳两个台阶不就是dp[i]了么。
那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!

所以dp[i] = dp[i - 1] + dp[i - 2] 。
在推导dp[i]的时候,⼀定要时刻想着dp[i]的定义,否则容易跑偏。这体现出确定dp数组以及下标的含义的重要性!
3. dp数组如何初始化
在回顾⼀下dp[i]的定义:爬到第i层楼梯,有dp[i]中⽅法。
那么i为0,dp[i]应该是多少呢,这个可以有很多解释,但都基本是直接奔着答案去解释的。
例如强⾏安慰⾃⼰爬到第0层,也有⼀种⽅法,什么都不做也就是⼀种⽅法即:dp[0] = 1,相当于直接站在楼顶。
但总有点牵强的成分。
那还这么理解呢:我就认为跑到第0层,⽅法就是0啊,⼀步只能⾛⼀个台阶或者两个台阶,然⽽楼层是0,直接站楼顶上了,就是不⽤⽅法,dp[0]就应该是0.
其实这么争论下去没有意义,⼤部分解释说dp[0]应该为1的理由其实是因为dp[0]=1的话在递推的过程中i从2开始遍历本题就能过,然后就往结果上靠去解释dp[0] = 1。
从dp数组定义的⾓度上来说,dp[0] = 0 也能说得通。
需要注意的是:题⽬中说了n是⼀个正整数,题⽬根本就没说n有为0的情况。
所以本题其实就不应该讨论dp[0]的初始化!
我相信dp[1] = 1,dp[2] = 2,这个初始化⼤家应该都没有争议的。
所以我的原则是:不考虑dp[0]如果初始化,只初始化dp[1] = 1,dp[2] = 2,然后从i = 3开始递推,这样才符合dp[i]的定义。
4. 确定遍历顺序
从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序⼀定是从前向后遍历的
5. 举例推导dp数组
举例当n为5的时候,dp table(dp数组)应该是这样的

如果代码出问题了,就把dp table 打印出来,看看究竟是不是和⾃⼰推导的⼀样。此时⼤家应该发现了,这不就是斐波那契数列么!唯⼀的区别是,没有讨论dp[0]应该是什么,因为dp[0]在本题没有意义!
以上五部分析完之后,C++代码如下:

class Solution {
public:
    int climbStairs(int n) {
        if(n <= 1)
            return n;
        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];
    }
};

时间复杂度:O(n)
空间复杂度:O(n)

使用最小花费爬楼梯oj链接

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 1:

输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。
- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。
示例 2:

输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。

思路:

使用动规五部曲:

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

到达第i个台阶所花费的最少体⼒为dp[i]

2. 确定递推公式

题目描述中说,可以选择向上一次爬一个台阶或者一次爬两个台阶,所以到达第i阶台阶,可以通过第i-1阶台阶到达或者是通过第i-1阶台阶到达,然后在根据题目描述求的是达到顶楼所需要花费的最小体力,所以递推公式可以推导出:

dp[i] = min(dp[i-1]+cost[i-1] + dp[i-2]+cost[i-2])

3. dp数组如何初始化

题目描述说,可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯,也就是说到下标为0的台阶和到下标为1的台阶是不需要花费体力的,所以应该初始化为:

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

4. 确定遍历顺序

本题的遍历顺序很简单,从前往后依次遍历就可以了

5.打印dp数组:

以上分析完毕,整体C++代码如下:

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

时间复杂度:O(n)
空间复杂度:O(n)

不同路径oj链接

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:


输入:m = 3, n = 7
输出:28
示例 2:

 

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
示例 3:

输入:m = 7, n = 3
输出:28
示例 4:

输入:m = 3, n = 3
输出:6

思路:

机器⼈从(0 , 0) 位置触发,到(m - 1, n - 1)终点。
按照动规五部曲来分析:

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

dp[i][j] :表⽰从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

2. 确定递推公式

根据题目中的描述机器人每次只能向下或者向右移动一步,所以到达dp[i][j]的路径总数是到达dp[i-1][j]的路径总数加上到达dp[i][j-1]的路径总数之和,所以递推公式应该为:dp[i][j] = dp[i-1][j] + dp[i][j-1]

3.dp数组的初始化

⾸先dp[i][0]⼀定都是1,因为从(0, 0)的位置到(i, 0)的路径只有⼀条,那么dp[0][j]也同理。

所以初始化代码为:

for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;

4. 确定遍历顺序

这⾥要看⼀下递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上⽅和左⽅推导⽽来,那么从左到右⼀层⼀层遍历就可以了。

5. 举例推导dp数组

如图所示:

以上动规五部曲分析完毕,C++代码如下:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>>dp;
        dp.resize(m);
        for (int i = 0; i < dp.size(); i++)
        {
            dp[i].resize(n, 0);
        }
        //初始化第一列:
        for (int i = 0; i < m; i++)
        {
            dp[i][0] = 1;
        }
        //初始化第一行:
        for (int j = 0; j < n; j++)
        {
            dp[0][j] = 1;
        }
        for (int i = 1; i < m; i++)
        {
            for (int j = 1; j < n; j++)
            {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
};

时间复杂度:O(m * n)
空间复杂度:O(m * n)

不同路径2oj链接

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

示例 1:


输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
示例 2:

 
输入:obstacleGrid = [[0,1],[0,0]]
输出:1

思路:

通过动规五部曲来进行分析:

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

dp[i][j] :表⽰从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

2. 确定递推公式

递推公式和62.不同路径⼀样,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。

但这⾥需要注意⼀点,因为有了障碍,(i, j)如果就是障碍的话应该就保持初始状态(初始状态为0)。

所以代码为:

if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}

3. dp数组如何初始化

因为从(0, 0)的位置到(i, 0)的路径只有⼀条,所以dp[i][0]⼀定为1,dp[0][j]也同理。但如果(i, 0) 这条边有了障碍之后,障碍之后(包括障碍)都是⾛不到的位置了,所以障碍之后的dp[i][0]应该还是初始值0。

 下标(0, j)的初始化情况同理。所以本题初始化代码为:

vector<vector<int>> dp(m, vector<int>(n, 0));
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循环的终⽌条件,⼀旦遇到obstacleGrid[i][0] == 1的情况就停⽌dp[i][0]的赋值1的操作,dp[0][j]同理

4. 确定遍历顺序

从递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 中可以看出,⼀定是从左到右⼀层⼀层遍历,这样保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][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];
}
}

5. 举例推导dp数组

动规五部分分析完毕,对应C++代码如下:

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        vector<vector<int>>dp;
        dp.resize(m);
        for(int i = 0; i < dp.size(); i++)
        {
            dp[i].resize(n,0);
        }
        //初始化第一行:
        for(int i = 0; i < n; i++)
        {
            if(obstacleGrid[0][i] == 1)
                break;
            dp[0][i] = 1; 
        }
        //初始化第一列:
        for(int j = 0; j < m; j++)
        {
            if(obstacleGrid[j][0] == 1)
                break;
            dp[j][0] = 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];
    }
};

时间复杂度O(n * m) n m 分别为obstacleGrid 长度和宽度
空间复杂度O(n * m)

整数拆分oj链接

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积 。

示例 1:

输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:

输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

思路:

使用动规五部曲进行分析:

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

dp[i]:分拆数字i,可以得到的最⼤乘积为dp[i]。

2. 确定递推公式

可以想 dp[i]最⼤乘积是怎么得到的呢?其实可以从1遍历j,然后有两种渠道得到dp[i].
⼀个是j * (i - j) 直接相乘。
⼀个是j * dp[i - j],相当于是拆分(i - j)

那么从1遍历j,⽐较(i - j) * j和dp[i - j] * j 取最⼤的。
递推公式:
dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

3. dp的初始化

dp[0] dp[1]应该初始化多少呢?
有的题解⾥会给出dp[0] = 1,dp[1] = 1的初始化,但解释⽐较牵强,主要还是因为这么初始化可以把题⽬过了。
严格从dp[i]的定义来说,dp[0] dp[1] 就不应该初始化,也就是没有意义的数值。
拆分0和拆分1的最⼤乘积是多少?这是⽆解的。
这⾥我只初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最⼤乘积是1,这个没有任何异议!

4. 确定遍历顺序

确定遍历顺序,先来看看递归公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));dp[i] 是依靠 dp[i - j]的状态,所以遍历i⼀定是从前向后遍历,先有dp[i - j]再有dp[i]。枚举j的时候,是从1开始的。i是从3开始,这样dp[i - j]就是dp[2]正好可以通过我们初始化
的数值求出来。

所以遍历顺序为

for (int i = 3; i <= n ; i++) {
for (int j = 1; j < i - 1; j++) {
dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
}
}

5. 举例推导dp数组

举例当n为10 的时候,dp数组⾥的数值,如下:

以上动规五部曲分析完毕,C++代码如下:

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

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

不同的二叉搜索树oj链接

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:

输入:n = 3
输出:5

示例 2:

输入:n = 1
输出:1

思路:

这道题⽬描述很简短,但估计⼤部分同学看完都是懵懵的状态,这得怎么统计呢?面对这道题,我们应该画画图,看看有没有什么规律

 

 n为3,可以有5颗二叉搜索树:

来看看n为3的时候,有哪⼏种情况。

当1为头结点的时候,其右⼦树有两个节点,看这两个节点的布局,是不是和 n 为2的时候两棵树的布局是⼀样的啊!

(可能有同学问了,这布局不⼀样啊,节点数值都不⼀样。别忘了我们就是求不同树的数量,并不⽤把搜索树都列出来,所以不⽤关⼼其具体数值的差异)

当3为头结点的时候,其左⼦树有两个节点,看这两个节点的布局,是不是和n为2的时候两棵树的布局也是⼀样的啊!

当2位头结点的时候,其左右⼦树都只有⼀个节点,布局是不是和n为1的时候只有⼀棵树的布局也是⼀样的啊!

发现到这⾥,其实我们就找到的重叠⼦问题了,其实也就是发现可以通过dp[1] 和 dp[2] 来推导出来dp[3]的某种⽅式。
思考到这⾥,这道题⽬就有眉⽬了。

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

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

有2个元素的搜索树数量就是dp[2]。
有1个元素的搜索树数量就是dp[1]。
有0个元素的搜索树数量就是dp[0]。

所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]

如图所示:

 此时我们已经找到的递推关系了,那么可以⽤动规五部曲在系统分析⼀遍。

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

dp[i] : 1到i为节点组成的⼆叉搜索树的个数为dp[i]。

2. 确定递推公式

在上⾯的分析中,其实已经看出其递推关系, dp[i] += dp[以j为头结点左⼦树节点数量] *dp[以j为头结点右⼦树节点数量]
j相当于是头结点的元素,从1遍历到i为⽌。

所以递推公式:dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左⼦树节点数量,i-j 为以j为头结点右⼦树节点数量

3. dp数组如何初始化

初始化,只需要初始化dp[0]就可以了,推导的基础,都是dp[0]。那么dp[0]应该是多少呢?
从定义上来讲,空节点也是⼀颗⼆叉树,也是⼀颗⼆叉搜索树,这是可以说得通的。

4. 确定遍历顺序

⾸先⼀定是遍历节点数,从递归公式:dp[i] += dp[j - 1] * dp[i - j]可以看出,节点数为i的状态是依靠 i之前节点数的状态。
那么遍历i⾥⾯每⼀个数作为头结点的状态,⽤j来遍历。

代码如下:

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= i; j++) {
        dp[i] += dp[j - 1] * dp[i - j];
    }
}

5. 举例推导dp数组

n为5时候的dp数组状态如图:

 综上分析完毕,C++代码如下:

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357024.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【日常点滴019】Python制作流浪气球游戏(导弹射击类)

Python制作流浪气球游戏&#xff08;导弹射击类&#xff09;教学课程代码&#xff08;分步教学版&#xff09;1、构建全局通用代码结构2、构建气球精灵类3、构建导弹精灵类4、碰撞检测5、构建游戏信息类 &#xff08;最终完整代码&#xff09;教学课程代码&#xff08;分步教学…

基于springboot+vue的食疗系统

基于springbootvue的食疗系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&…

java面试题-并发基础

1.多线程的出现是要解决什么问题的? 本质什么?提高程序性能&#xff1a;单线程程序只能按照固定的顺序依次执行每个任务&#xff0c;无法同时处理多个任务。多线程技术可以在同一时间内执行多个任务&#xff0c;从而提高程序的运行效率和响应速度。提高程序的并发性&#xff…

前端借助Canvas实现压缩图片两种方法

一、具体代码 1、利用canvas压缩图片方法一 // 第一种压缩图片方法&#xff08;图片base64,图片类型,压缩比例,回调函数&#xff09;// 图片类型是指 image/png、image/jpeg、image/webp(仅Chrome支持)// 该方法对以上三种图片类型都适用 压缩结果的图片base64与原类型相同// …

Python自动化测试-使用Pandas来高效处理测试数据

Python自动化测试-使用Pandas来高效处理测试数据 目录&#xff1a;导读 一、思考 二、使用pandas来操作Excel文件 三、使用pandas来操作csv文件 四、总结 一、思考 1.Pandas是什么&#xff1f; 功能极其强大的数据分析库可以高效地操作各种数据集 csv格式的文件Excel文件H…

Python3-基本数据类型

Python3 基本数据类型 Python 中的变量不需要声明。每个变量在使用前都必须赋值&#xff0c;变量赋值以后该变量才会被创建。 在 Python 中&#xff0c;变量就是变量&#xff0c;它没有类型&#xff0c;我们所说的"类型"是变量所指的内存中对象的类型。 等号&…

注意力机制笔记——结合沐神和B站老弓up主

B站【大白话浅谈【注意力机制】】 聚类 是针对 样本, 注意力机制是针对样本相关性,来进行计算的 自注意力机制 指的是 query ,key,value都是同一个部分。 可以学到 类似的 短语 ,和 语义特征。如its 指代的对象。 评论区大佬 根据这篇论文《Effective Approaches to…

[ vulhub漏洞复现篇 ] Drupal 远程代码执行漏洞(CVE-2018-7602)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

设计模式之各种设计模式总结与对比

目录1 目标2 定位3 一句话归纳设计原则4 G0F 23种设计模式简介5 设计模式使用频次总结6 —句话归纳设计模式7 设计模式之间的关联关系和对比1 目标 1、 简要分析GoF 23种设计模式和设计原则,做整体认知。 2、 剖析Spirng的编程思想&#xff0c;启发思维,为之后深入学习Spring…

利用React实现多个场景下的鼠标跟随框提示框

前言 鼠标跟随框的作用如下图所示&#xff0c;可以在前端页面上&#xff0c;为我们后续的鼠标操作进行提示说明&#xff0c;提升用户的体验。本文将通过多种方式去实现&#xff0c;从而满足不同场景下的需求。 实现原理 实现鼠标跟随框的原理很简单&#xff0c;就是监听鼠标在…

删除链表的倒数第N个节点

题目描述19. 删除链表的倒数第 N 个结点难度中等2410收藏分享切换为英文接收动态反馈给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。示例 1&#xff1a;输入&#xff1a;head [1,2,3,4,5], n 2输出&#xff1a;[1,2,3,5]示例 2&#…

【ROS】Windows系统安装ROS体验

大家平时玩ROS都是在Ubuntu系统上&#xff0c;那Windows系统可以安装吗&#xff0c;答案是&#xff1a;可以的&#xff01;Windows为了发展自家的物联网生态&#xff0c;已经在Windows系统支持ROS了。 文章目录1.安装VS 20172.安装Chocolatey & Git3.安装ROS4.运行ROS例程1…

RabbitMQ核心内容:实战教程(java)

文章目录一、安装二、入门1.分类2.核心概念3.工作原理4.六大模式三、模式一&#xff1a;"Hello World!"1.依赖2.生产者代码3.消费者代码四、模式二&#xff1a;Work Queues1.工作原理2.工具类代码&#xff1a;连接工厂3.消费者代码4.生产者代码5.分发策略不公平分发预…

计算机网络第2章(物理层)学习笔记

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

Metasploit 使用篇

文章目录前言一、msfconsole启动msfconsole命令分类核心命令模块命令作业命令资源脚本命令后台数据库命令二、使用案例更改提示和提示字符运行shell命令信息收集&#xff1a;HTTP头检测前言 理解了Meatasploit框架架构、原理之后&#xff0c;自然就很好理解它的使用逻辑 find…

springmvc java ssm药店库存进销存管理系统带前台

基于JSP技术、SSM框架、B/S机构、Mysql数据库设计并实现了龙康药店管理系统。系统主要包括药店简介管理、客户信息管理、药品信息管理、入库信息管理、出库信息管理、进货单管理等功能模块。其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#…

Games101-202作业1

一. 将模型从模型空间变换到世界空间下 在这个作业下&#xff0c;我们主要进行旋转的变换。 二.视图变换 ,将相机移动到坐标原点&#xff0c;同时保证物体和相机进行同样的变换&#xff08;这样对形成的图像没有影响&#xff09; 在这个作业下我们主要进行摄像机的平移变换&am…

【深度学习编译器系列】1. 为什么需要深度学习编译器?

本系列是自学深度学习编译器过程中的一些笔记和总结&#xff0c;参考文献在文末。 1. 概述 深度学习&#xff08;DL&#xff09;编译器的产生有两方面的因素&#xff1a;深度学习模型的广泛应用&#xff0c;以及深度学习芯片的层出不穷。 一方面&#xff0c;我们现在有非常多…

剑指 Offer 34. 二叉树中和为某一值的路径(java解题)

剑指 Offer 34. 二叉树中和为某一值的路径&#xff08;java解题&#xff09;1. 题目2. 解题思路3. 数据类型功能函数总结4. java代码1. 题目 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶…

关于MySQL的limit优化

1、前提 提示&#xff1a;只适用于InnoDB引擎 2、InnoDB存储特点 它把索引和数据放在了一个文件中&#xff0c;就是聚集索引。这与MyISAM引擎是不一样的。 3、SQL示例 -- 给cve字段建立索引 select * from cnnvd where cveCVE-2022-24808 limit 300000,10&#xff1b;由于M…