【蓝桥杯】最难算法没有之一· 动态规划真的这么好理解?(引入)

news2024/11/13 20:40:18

  

欢迎回到:遇见蓝桥遇见你,不负代码不负卿!  

目录

一、何为动态规划DP

二、记忆化搜索

典例1.斐波那契数列

方法一:暴力递归

方法二:记忆化搜索

变形题

典例2:爬楼梯(青蛙跳台阶)

方法一:暴力递归 

方法二:记忆化搜索

变形题 

典例3.第N个泰波那契数

典例4.Function

三、递推

1.递归

2.递推

典例5.骨牌问题

典例6.杨辉三角

典例7.数字三角形

四、蓝桥结语:遇见蓝桥遇见你,不负代码不负卿。


推荐老铁们两个学习网站:

面试利器&算法学习:牛客网

风趣幽默的学人工智能:人工智能学习

【前言】

在学习动态规划之前,我们必须要先掌握记忆化搜索和递推,这两块东西搞好了之后,面对动态规划那就容易多啦!好,接下来向铁汁们详细介绍这两块内容,走着。

  

哈哈,昨天在群里发现这张表清包,觉得很好玩,特意放到这里皮一下,嘿嘿,这下真的要开始咯。

一、何为动态规划DP

动态规划(英语:Dynamic programming,简称 DP),通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 (是不是很像前面讲解过的一种算法——分治,其实可以认为动态规划就是特殊的分治)

动态规划常常适用于有重叠子问题和最优子结构性质的问题,并且记录所有子问题的结果,因此动态规划方法所耗时间往往远少于暴力递归解法。

使用动态规划解决的问题有个明显的特点,一旦一个子问题的求解得到结果,以后的计算过程就不会修改它,这样的特点叫做无后效性,求解问题的过程形成了一张有向无环图。动态规划只解决每个子问题一次,具有天然剪枝的功能,从而减少计算量

动态规划有自底向上和自顶向下两种解决问题的方式。自顶向下即记忆化搜索,自底向上就是递推。

好,那么本文主要讲解的记忆化搜索和递推也就浮出水面了,下面详细介绍(请放心,后面会详细讲解动态规划的,这部分既重要又很难,所以需要慢慢去理解。)


二、记忆化搜索

记忆化搜索的本质:动态规划。

记忆化搜索是动态规划的一种实现方式,记忆化搜索是用搜索的方式实现了动态规划,因此记忆化搜索就是动态规划。

提问:

何为记忆化搜索?

回答:

顾名思义,记忆化搜索肯定也就和“搜索”脱不了关系, 前面讲解的递归、DFS和BFS想必大家都已经掌握的差不多了,它们有个最大的弊病就是:低效!原因在于没有很好地处理重叠子问题。那么对于记忆化搜索呢,它虽然采用搜索的形式,但是它还有动态规划里面递推的思想,巧就巧在它将这两种方法很好的综合在了一起,简单又实用。

记忆化搜索,也叫记忆化递归,其实就是拆分子问题的时候,发现有很多重复子问题,然后再求解它们以后记录下来。以后再遇到要求解同样规模的子问题的时候,直接读取答案。

其实可以浅显的认为记忆化搜索就是简单DP,因为实在是太像了。 


下面详细讲解四道例题,让大家深入理解上面的概念:

典例1.斐波那契数列

原题链接:力扣

题目描述:

示例1:

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

示例2:

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

方法一:暴力递归

代码执行:

class Solution {
public:
    int fib(int n){
        //方法一:暴力递归
        //找边界
        if(n == 0){
            return 0;
        }
        if(n == 1){
            return 1;
        }
        return fib(n - 1) + fib(n - 2);
    }
};

不用多说,学校老师讲递归的时候似乎都是拿这个举例。我们也知道这样写代码虽然简洁易懂,但是十分低效,低效在哪里?假设 n = 20,请画出递归树:

很显然,进行了大量的重复计算,虽然本题是一个很好的用来讲解递归的例子,但是嘞,本题在实际运用当中用递归来做那就太不明智啦,所以,这里就需要引入一个带有「备忘录」的递归,也就是前面所提到的记忆化搜索,下面看看具体怎么操作:

方法二:记忆化搜索

即然低效的原因是重复计算,那么我们可以造一个「备忘录」,每次算出某个子问题的答案后别急着返回,先记到「备忘录」里再返回;每次遇到一个子问题先去「备忘录」里查一查,如果发现之前已经解决过这个问题了,直接把答案拿出来用,不要再耗时去计算了。

一般使用一个数组充当这个「备忘录」,当然你也可以使用哈希表(字典),思想都是一样的。

这样去处理的话就无需进行重复计算了,相比前面的暴力递归就显得高效明智得多,而且很容易理解,不信你看:

代码执行:

class Solution {
public:
    int fib(int n){
        //方法三:记忆化搜索(简单DP)
        //找边界
        if(n == 0){
            return 0;
        }
        if(n == 1){
            return 1;
        }
        //需要定义一个大小为(n+1)的整形数组,并且初始化为0
        //之所以是n+1,是因为要使用到n这个下标
        vector<int> dp(n+1, 0);
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2; i < n+1; i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

当然也有这样的变形,多了一个要求,很简单的,大家看一下:

变形题

代码执行:

//找边界
        if(n == 0)
            return 0;
        if(n == 1)
            return 1;
        vector<int> dp(n + 1, 0);//开辟一个大小为n+1的整形数组(因为需要使用下标n),并且初始化为0
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2; i < n+1; i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2];
            dp[i] = dp[i] % 1000000007;
        }
        return dp[n];
    }

实际上,这种解法和迭代的动态规划非常相似,只不过这种方法叫做「自顶向下」,动态规划叫做「自底向上」。

啥叫「自顶向下」?

注意我们刚才画的递归树(或者说图),是从上向下延伸,都是从一个规模较大的原问题比如说 f(20),向下逐渐分解,直至 f(1) 和 f(2) 这两个 base case,然后逐层返回答案,这就叫「自顶向下」。

啥叫「自底向上」?

反过来,我们直接从最底下,最简单,问题规模最小的 f(1) 和 f(2) 开始往上推,直到推到我们想要的答案 f(20),这就是动态规划的思路,这也是为什么动态规划一般都脱离了递归,而是由循环迭代完成计算。


典例2:爬楼梯(青蛙跳台阶)

原题链接:力扣

题目描述:

示例1:

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

示例2:

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

首先分析一下本题:

注意:这个题目问的是什么?

问的不是能爬多少次,而是有多少种方法能到最后一个台阶。

问题分析:

当n > 2时,第一次爬就有两种不同的选择:一是第一次只爬一级,此时爬法数目等于后面剩下的(n - 1)级台阶的爬法数目,即为f(n - 1); 还有一种选择是第一次爬两级,此时爬法数目等于后面剩下的(n - 2)级台阶的爬法数目,即为f(n - 2).

所以有:f(n) = f(n - 1) + f(n - 2)

当n == 1时,有1种爬法;

当n == 2时,有2种爬法;

当n == 3时,有3种爬法;

当n == 4时,有5种爬法。

是呀,这题跟斐波那契数列基本上一样,不过这道题目需要思考一下,没有斐波那契数列这么明显。但是需要注意的是,递归边界还是有所不同的哦!

方法一:暴力递归 

代码执行:

class Solution {
public:
    int climbStairs(int n) {
        //方法一:暴力递归
        //找边界
        if(n == 1){
            return 1;
        }
        if(n == 2){
            return 2;
        }
        return climbStairs(n - 1) + climbStairs(n - 2);
    }
};

方法二:记忆化搜索

代码执行:

class Solution {
public:
    int climbStairs(int n) {
        //方法二:记忆化搜索(简单DP)
        //找边界
        if(n == 1){
            return 1;
        }
        if(n == 2){
            return 2;
        }
        //定义一个大小为n+1的整型数组,并且初始化为0
        vector<int> dp(n+1, 0);
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3; i < n+1; i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

变形题 

其实就是多了一个要求...

代码执行:

class Solution {
public:
    int numWays(int n) {
        //找边界
        if(n == 0 || n == 1)
            return 1;
        if(n == 2)
            return 2;
        vector<int> dp(n+1, 0);
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3; i < n+1; i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2];
            dp[i] %= 1000000007;
        }
        return dp[n];
    }
};

典例3.第N个泰波那契数

原题链接:力扣

题目描述: 

示例1:

输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4

示例2:

输入:n = 25
输出:1389537

代码执行:

class Solution {
public:
    int tribonacci(int n) {
        //找边界
        if(n == 0)
            return 0;
        if(n == 1 || n == 2)
            return 1;
        //定义一个大小为n+1的整形数组,并将其初始为0
        vector<int> dp(n+1, 0);
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 1;
        for(int i = 3; i < n+1;i++)
        {
            dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1];//递推公式
        }
        return dp[n];
    }
 
};

上面这三道题是非常简单的,理解了上面的题目,下面这道题肯定就会非常容易了。


典例4.Function

原题链接: Function - 洛谷

题目描述:

输入格式:

输入有若干行。并以−1,−1,−1结束。

保证输入的数在[−9223372036854775808,9223372036854775807]之间,并且是整数。

输出格式

输出若干行,每一行格式:w(a, b, c) = ans

思路:

本题重在理解题意,题目不难,但是要把题目多读几遍。 

代码执行:

#include <stdio.h>

#define LL long long

LL dp[25][25][25];

LL w(LL a, LL b, LL c)
{
    //两个特殊情况判断
	if(a <= 0 || b <= 0 || c <= 0) 
	    return 1;
	if(a > 20 || b > 20 || c > 20) 
	    return w(20, 20, 20);
	
	if(a < b && b < c)
	{
		if(dp[a][b][c-1] == 0)
		{
			dp[a][b][c-1] = w(a, b, c-1);
		}
		if(dp[a][b-1][c-1] == 0)
		{
			dp[a][b-1][c-1] = w(a, b-1 ,c-1);
		}
		if(dp[a][b-1][c] == 0)
		{
			dp[a][b-1][c] = w(a, b-1, c);
		}
		dp[a][b][c] = dp[a][b][c-1] + dp[a][b-1][c-1] - dp[a][b-1][c];
	}
	else
	{
		if(dp[a-1][b][c] == 0)
		{
			dp[a-1][b][c] = w(a-1, b, c);
		}
		if(dp[a-1][b-1][c] == 0)
		{
			dp[a-1][b-1][c] = w(a-1, b-1 ,c);
		}
		if(dp[a-1][b][c-1] == 0)
		{
			dp[a-1][b][c-1] = w(a-1, b, c-1);
		}
		if(dp[a-1][b-1][c-1] == 0)
		{
			dp[a-1][b-1][c-1] = w(a-1, b-1, c-1);
		}
		dp[a][b][c] = dp[a-1][b][c] + dp[a-1][b][c-1] + dp[a-1][b-1][c] - dp[a-1][b-1][c-1];
	}
	
	return dp[a][b][c];
}

int main()
{
	LL a, b, c;
	
	while(scanf("%lld%lld%lld", &a, &b, &c) != EOF)
	{
		if(a == -1 && b == -1 && c == -1) 
		    return 0;//当输入的值为-1 -1 -1时直接结束循环
		
		printf("w(%lld, %lld, %lld) = ", a, b, c);
		printf("%lld\n", w(a, b, c));
	}
}

三、递推

看到“递推”,大家肯定能联想到“递归”,好嘞,接下来向大家详细讲解递推和递归有哪些区别。

1.递归

如果大家对于递归部分掌握的不是很好的话可以看看我之前的博文哦,里面的基础概念讲解的很详细,而且还配备大量练习题。

蓝桥杯算法竞赛系列第二章——深入理解重难点之递归(上)_安然无虞的博客-CSDN博客一、递归是什么https://blog.csdn.net/weixin_57544072/article/details/120836167

递归是大问题转化为小问题,不断调用自身或不断被间接调用的一类算法。

1.递归算法的关键是要找出大问题和小问题的联系----即找重复,进而使大问题的规模不断减小,直至可以被直接解决。

2.递归算法的另一个关键点是终止条件----即找边界,这个是十分重要的。

有时,递归算法的效率会很低,这时候就可以用上面所说的记忆化搜索,即建立一个数组,用来记录每次递归得到的答案,这样如果后面要继续使用这个值的时候,就不用再次计算了,也就避免了重复计算问题。


2.递推

部分定义和题解参考博客链接:

递推算法-五种典型的递推关系_lybc2019的博客-CSDN博客_递推法

递推和递归非常相似。

递推是把问题划分为若干个步骤,每个步骤之间,或者是这个步骤与之前的几个步骤之间有一定的数量关系,可以用前几项的值表示出这一项的值,这样就可以把一个复杂的问题变成很多小的问题。

递推算法注意的是设置什么样的递推状态,因为一个好的递推状态可以让问题很简单。最难的是想出递推公式,一般递推公式是从后面向前想,倒推回去。


典例5.骨牌问题

题目描述:

有 2*n的一个长方形方格,用一个1*2的骨牌铺满方格。请编写一个程序,试对给出的任意一个n(n>0), 输出铺法总数。 

思路:

其实这道题目很简单的,找到递推公式即可,跟上面的爬楼梯问题很相似,这里就详细分析一下:

n = 1 时,只有一种铺法
n = 2 时,如下图,有全部竖着铺和横着铺两种
n = 3 时,骨牌可以全部竖着铺,也可以认为在方格中已经有一个竖铺的骨牌,则需要在方格中排列两个横排骨牌(无重复方法),若已经在方格中排列两个横排骨牌,则必须在方格中排列一个竖排骨牌。如下图,再无其他排列方法,因此铺法总数表示为三种。
通过上面的分析,不难看出规律:f(3) = f(1) + f(2)

所以可以的得到递推关系:f(n) = f(n - 1) + f(n - 2)

  

代码执行:

class Solution {
public:
    int brand(int n) {
        
        //找边界
        if(n == 1){
            return 1;
        }
        if(n == 2){
            return 2;
        }
        //定义一个大小为n+1的整型数组,并且初始化为0
        vector<int> dp(n+1, 0);
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3; i < n+1; i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2];//找出递推关系
        }
        return dp[n];
    }
};

典例6.杨辉三角

原题链接:力扣

题目描述: 

示例1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例2:

输入: numRows = 1
输出: [[1]]

思路:

本题比较简单,很容易就能看出递推关系,从第三行第二列开始,每个数是它左上方和右上方的数的和。 

代码执行:

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int> >ret(numRows);//定义一个二维数组用于存放结果
        //首先将第一列和最后一列元素全部赋值为1
        for(int i = 0; i < numRows; i++)
        {
            ret[i].resize(i+1);//resize()的作用就是为一维数组分配空间
            ret[i][0] = ret[i][i] = 1;
            //从第三行第二列开始有递推关系:ret[i][j] = ret[i+1][j]+ret[i+1][j+1];
            for(int j = 1; j < i; j++)
            {
                ret[i][j] = ret[i-1][j] + ret[i-1][j-1];
            }
        }
        return ret;
    }
};

代码中需要注意的是:vector 中的resize() 是重新分配空间的。


典例7.数字三角形

原题链接:[USACO1.5][IOI1994]数字三角形 Number Triangles - 洛谷

题目描述:

输入格式:

第一个行一个正整数 n ,表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。

输出格式:

单独的一行,包含那个可能得到的最大的和。

数据范围:

1 ≤ n ≤ 1000,三角形数字值在 [0,100] 范围内。 

示例:

输入:

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5 

输出:

30

思路:

本题采用倒推的方式:

假设func[i][j]表示的是从 i, j 到最后一层的最大路径之和

当从顶层沿某条路径走到第i层向第i+1层前进时,我们的选择是沿其下两条可行路径中最大数字的方向前进,所以找出递推关系:func[i][j] += max(func[i+1][j],func[i+1][j+1]);
注意:func[i][j]表示当前数字的值,func[i+1][j]和func[i+1][j+1]分别表示从i+1,j、i+1,j+1到最后一层的最大路径之和;
最终func[0][0]就是所求

代码执行:

#include<stdio.h>
#include<algorithm>
using namespace std;
 
int func[1005][1005] = {0};
 
int main()
{
    int n = 0;
    scanf("%d", &n);
    int i = 0;
    int j = 0;
    for(i = 0; i < n; i++)
    {
        for(j = 0; j <= i; j++)
        {
            scanf("%d", &func[i][j]);
        }
    }
    //假设func[i][j]表示的是从i, j到最后一层的最大路径之和
    //找出递推关系:func[i][j]+=max(func[i+1][j],func[i+1][j+1]);
    //func[i][j]表示当前数字的值,func[i+1][j]和func[i+1][j+1]分别表示从i+1,j、i+1,j+1到最后一层的最大路径之和
    //最终func[0][0]就是所求
    for(i = n - 2; i >= 0; i--)
    {
        for(j = 0; j <= i; j++)
        {
            func[i][j] += max(func[i+1][j], func[i+1][j+1]);
        }
    }
    printf("%d\n", func[0][0]);
    return 0;
}

  

四、蓝桥结语:遇见蓝桥遇见你,不负代码不负卿。

又到了说再见的时候了,上面是动规引入部分,看起来不难,但其实动态规划算法难起来是真变态,后面会慢慢深入,不过蓝桥几乎不会考到特别难的动规,正常都是简单DP,所以后面会有大量的练习侧重于简单DP。OK,今天到这就结束咯。886

求求兄弟姐妹们 · 赏俺个三连吧~

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

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

相关文章

海睿思分享 | 低而不LOW的低代码开发

低代码&#xff08;Low-Code&#xff09; 是指输出最少的代码&#xff0c;快速完成软件系统开发&#xff0c;进而实现降低开发成本的成效。 Low-Code 中的“Low”与网络热词“LOW”同音同字&#xff0c;前者通常理解为低成本或少量的代码输出&#xff0c;后者是对低认知已见问…

【数据结构】数据结构小试牛刀之单链表

【数据结构】数据结构小试牛刀之单链表 一、目标二、实现1、初始化工作2、单链表的尾插2.1、图解原理2.2、代码实现解答一个疑问 3、单链表的尾删3.1、图解原理3.2、代码实现 4、打印单链表5、单链表的头插5.1、图解原理5.2、代码实现 6、单链表的头删6.1、图解原理6.2、代码实…

【Linux系统】理解Linux中进程间通信

Linux进程间通信 1 进程间通信的介绍1.1为什么要有进程间通信1.2 为什么能进程间通信 2 进程间通信的框架2.1 进程间通信机制的结构2.2 进程间通信机制的类型2.2.1 共享内存式2.2.2 消息传递式 2.3 进程间通信的接口设计 3 进程间通信机制简介4 详细讲解进程间通信部分机制&…

【OAuth2.0 Client 总结】对接github第三方登录以及其他第三方登录总结

之前搞 oauth 登录一直没有搞好&#xff0c;客户端、授权服务端、资源端一起搞对于我刚接触的小菜鸡来说&#xff0c;难度有点大。 然后就先搞了个 Client 端对接 Github 登录。 网上关于 Github 登录的资料有很多&#xff0c;而且框架对 Github 集成的也很好&#xff0c;配置起…

【深入解析K8S专栏介绍】

序言 时间永远是旁观者&#xff0c;所有的过程和结果&#xff0c;都需要我们自己去承担。 Kubernetes (k8s) 是一个容器编排平台&#xff0c;允许在容器中运行应用程序和服务。 专栏介绍 欢迎订阅&#xff1a;【深入解析k8s】专栏 简单介绍一下这个专栏要做的事&#xff1a; 主…

8年测试老兵竟被面试官10分钟pass,这也太难了吧...

前言 随着软件测试领域对于技术要求越来越清晰&#xff0c;到现在测试人员在市场上的岗位需求也变得越来越复杂。极大部分的企业都开始对自动化测试岗位有了更多的需要。自然而然&#xff0c;面试就相对于非常重要了。 笔试部分 1.阐述软件生命周期都有哪些阶段&#xff1f;…

stm32cubemx IAP升级(二)

stm32cubemx IAP升级- App的制作 板卡&#xff1a;Nucleo-L412 平台&#xff1a;macbook pro 工具&#xff1a;vscode stm32cubemx stm32cubeProgramer cmake toolchain 整体思路 将App设置为从0x08007000地址启动&#xff0c;然后初始化一路串口用作接收上位机的升级数据&a…

docker容器:docker镜像的三种创建方法及dockerfile案例

目录 一、基于现有镜像创建 1、创建启动镜像 2、生成新镜像 二、基于本地模板创建 1、OPENVZ 下载模板 2、导入容器生成镜像 三、基于dockerfile创建 1、dockerfile结构及分层 2、联合文件系统 3、docker镜像加载原理 4、dockerfile操作常用的指令 (1)FROM指令 (…

kotlin协程flow retry retryWhen(2)

kotlin协程flow retry retryWhen&#xff08;2&#xff09; 一、retry import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlockingfun main(args: Array<String>) {runBlocking {(1..5).asFlow().onEach {if (it 3) {println("-")throw Runti…

入行IC选择国企、私企还是外企?(内附各IC大厂薪资福利情况)

不少人想要转行IC&#xff0c;但不知道该如何选择公司&#xff1f;下面就来为大家盘点一下IC大厂的薪资和工作情况&#xff0c;欢迎大家在评论区补充。 一&#xff0e;老 牌 巨 头 在 IC 设计领域深耕许久&#xff0c;流程完善、技术扎实&#xff0c;公司各项制度都很完善、前…

关于改造维护工单BAPI_ALM_ORDER_MAINTAIN用于生产订单组件批量修改

1、研究背景 1.1、业务背景 由于销售、研发、工艺等需要频繁变更&#xff0c;导致工单中组件需要频繁的进行变更&#xff0c;修改组件的物料&#xff0c;数量&#xff0c;库存地点&#xff0c;工序等内容。 1.2、技术痛点 为了满足要求&#xff0c;使用了函数&#xff1a;CO…

FVM链的Themis Pro(0x,f4) 5日IDO超百万美元,或让Filecoin逆风翻盘

交易一直是DeFi乃至web3领域最经久不衰的话题&#xff0c;也因此催生了众多优秀的去中心化协议&#xff0c;如Uniswap和Curve。这些协议逐渐成为了整个系统的基石。 在永续合约方面&#xff0c;DYDX的出现将WEB2时代的订单簿带回了web3。其链下交易的设计&#xff0c;仿佛回到了…

为你的软件测试全职业生涯规划保驾护航

目录 前言 1. 软件测试行业现状和未来趋势 2. 从初级测试工程师到高级测试架构师的职业路径 3. 如何提升自身技能和素质 4. 如何管理好自己的职业生涯 总结 前言 作为一名软件测试人员&#xff0c;职业生涯规划是非常重要的。在这篇文章中&#xff0c;我将从以下几个方面…

Mac电脑安装apktool工具包

开发中少不了想看看别人怎么实现某个功能&#xff0c;于是会用到apktool反编译apk&#xff0c;apktool工具包是一个压缩包&#xff0c;下载后无需安装&#xff0c;简单配置下执行命令就能反编译apk了&#xff0c;下面我们看一下如何在Mac OS系统下如何安装apktool工具包&#x…

《Netty》从零开始学netty源码(四十)之SizeClasses

目录 SizeClasses SizeClasses 在netty中&#xff0c;内存会被切割成不同size的块&#xff0c;在分配的时候会根据所需的大小分配相应的内存大小&#xff0c;然而并不是所有的大小都会有相应大小的内存块&#xff0c;比如想要11kb的内存&#xff0c;它并不会确切的给你11kb&am…

Mybatis高级映射及延迟加载

准备数据库表&#xff1a;一个班级对应多个学生。班级表&#xff1a;t_clazz&#xff1b;学生表&#xff1a;t_student 创建pojo&#xff1a;Student、Clazz // Student public class Student {private Integer sid;private String sname;//...... }// Clazz public class Cla…

我开发了一个温柔的智能客服聊天机器人ChatBot,并回答为什么不是ChatGPT(附思路和代码)

前言 若问2023年科技领域什么最火&#xff0c;那当然是ChatGPT了&#xff0c;这么智能的对话机器人&#xff0c;给人带来无限的想象&#xff0c;围绕着ChatpGPT的各种热点和创意层出不穷。作为一个多年从事编程开发的程序员&#xff0c;我对于这么大的一个热点也很兴奋&#x…

视频虚拟主播怎们搞?体验报告全记录;一图掌握SD应用精髓;Chat效率工具大汇总;品牌营销进入AI时代 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『Stable Diffusion界面解读』一张图掌握SD使用精髓 ShowMeAI资源编号&#xff1a;No.R052 图片可能被平台压缩&#xff0c;加入知识星…

安装配置 JupyterLab ubuntu20.04

目录 ​编辑 &#xff08;1&#xff09;安装 &#xff08;2&#xff09;配置 &#xff08;1&#xff09;生成配置文件 &#xff08;2&#xff09;生成jupyterlab的登录密码 &#xff08;3&#xff09;修改 jupyter 的配置文件 &#xff08;4&#xff09;安装 jupyterlab…

Mybatis(三)

1、mybatis中的连接池以及事务控制 原理部分了解&#xff0c;应用部分会用 mybatis中连接池使用及分析 mybatis事务控制的分析2、mybatis基于XML配置的动态SQL语句使用 会用即可 mappers配置文件中的几个标签&#xff1a; <if> …