动态规划--斐波那契类型

news2025/3/9 15:45:11

目录

前言

1 第N个斐波那契数

2 爬楼梯

3 三步问题

4 使用最小花费爬楼梯

5 解码方法

总结

前言

本篇所讲的几个题目都是与斐波那契数的做法与思路类似的题目,所以直接放在一块解决了。

同时,由于第一次接触动态规划,我们也会讲解一下动态规划的做题的方法和步骤。

在动态规划中,我们第一步一般是会创建一个 dp 表,这个dp表一般是一个一维或者二维的数组。

然后我们就需要想办法将表中的数据填满。而表里面的某一项数据就是我们需要的结果。

上面是我们的做题的代码的顺序。而我们的做题思路则是: 

1 首先根据题目要求,确定dp表中每一项的状态表示。

2 分析题目要求,写出表中的某一项与他的前面或者后面的项的关联,也就是如何通过前面的项或者后面的项来得到该项,这个推导表达式就是我们所说的状态方程

3 分析dp表的边界情况。 一般来说,一维的dp表我们需要考虑头和尾。而二维的dp表我们考虑一下四条边。 同时,除了边界的情况,我们有时候也需要为其他的项设置一个初始的,不会影响填表的值。

4 确定填表顺序, 这个一般是根据我们的状态转移方程而来的,如果dp[i] 需要用到dp[i-1]等他之前的项,那么我们的填表顺序就是从前往后填表,以此类推。

5 确定返回值。 其实就是根据我们的状态表示和题目要求来决定表中的哪一项或者哪几项是我们需要的返回值。

而编写代码的步骤如下:

1 创建dp表

2 初始化边界

3 填表

4 返回

1 第N个斐波那契数

. - 力扣(LeetCode)

首先我们将动态规划的思想运用于简单的题型中。

对于斐波那契数这个题目,他会给我们一个 n ,我们需要返回第 n 个斐波那契数。

同时斐波那契数的递推公式题目已经给我们了,这其实就是我们所需要的状态转移方程。

那么我们不难想到,我们可以创建一个dp表,然后使用 dp[i] 来表示第 i 个斐波那契数。

根据题目中的递推关系我们可以得出 

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

同时这个表达式的前提是 i > 1 ,也就是说,第一个斐波那契数是不适用于这个表达式的,因为1-2<0 了,而没有第负数个斐波那契数这样的说法。

在这个题中,我们的填表顺序是从前往后,依次填每一个值,而由于 dp[0]=0 ,那么我们可以将整个dp表都初始化为 0 ,免得我们后续还要再来特殊处理dp[0]。同时需要将 dp[1] 设为 1 。

同时由于我们使用 dp[i] 来表示第 i 个斐波那契数,那么我们需要开 n+1 个空间,最终我们需要返回的是第 n 个斐波那契数,那么我们需要返回 dp[n]

那么我们的代码就是这样的:

class Solution {
public:
    int fib(int n) {
        //1 创建dp表
        vector<int> dp(n+1);
        //2 初始化边界
        dp[1] = 1;
        //3 填表
        for(int i = 2 ; i <= n ;++ i )
            dp[i] = dp[i-1] + dp[i-2];    
        //4 返回
        return dp[n];
    }
};

但是这样写有一个问题就是,我们的代码中是有 dp[1] = 1 这一行的,而我们的n其实可以是0,那么此时 dp[1] 就已经越界访问了,那么对于这样的特殊情况,我们可以提前返回。

class Solution {
public:
    int fib(int n) {
        //1 创建dp表
        vector<int> dp(n+1);
        //2 初始化边界
        if(n==0) return 0;  //返回特殊情况,防止越界访问
        dp[1] = 1;
        //3 填表
        for(int i = 2 ; i <= n ;++ i )
            dp[i] = dp[i-1] + dp[i-2];    
        //4 返回
        return dp[n];
    }
};

那么其实这个题做到这里就已经完了。 

但是这里我们还可以继续优化,由于dp[i] 只需要用到  dp[i-1] 和 dp[i-2] 这俩个值,不需要用到dp[i-3]及以前的数值,同时我们的填表是从前往后依次填的,那么就意味着,当 i 为 3 时,只需要用到 dp[1] 和 dp[2] ,不会用到dp[0] ,而当 i = 4 时,只需要用到 dp[2] 和 dp[3] ,dp[1] 和之前的数值已经不会再用到了,后续填表 i 是不断增大的,那么前面的值就再也不会用到。这其实是一种空间的浪费。

其实,我们只需要使用三个变量就能完成前面的思路。

我们定义 a , b , c 三个变量。

a 永远代表当前遍历到的 i 的 dp[i-2]

b 永远代表当前遍历到的 i 的 dp[i-1]

而c表示当前的 dp[i]

那么就有 c = a + b ; 

那么 i 更新的时候,就需要先更新 a = b , b = c ,那么这样一来,a永远是当前的 dp[i-2] ,b永远是当前的 dp[i-1]

最重要返回的就是 c 

那么还是一样的,特殊情况也就是 n = 0 的时候,此时要返回 a ,n = 1 的时候,此时要返回 b 。

因为a初始值就是 dp[0] ,b 初始值就是 dp[1] , 那么我们可以把c的初始值给为 dp[2] ,为了方便更新 b。 那么我们第一个填的就是 dp[3]了,也就是 i 需要从 3 开始。

        int a = 0 ,b = 1 ,c = 1 ;  // dp[0] dp[1] dp[2]
        if(n == 0) return a;
        if(n == 1) return b;
        for(int i = 3 ; i <= n; ++i)
        {
            a=b;
            b=c;
            c=a+b;
        }
        return c;

这种空间优化的方法我们称为滚动数组,就是使用几个变量,通过不断更新变量的值,在逻辑上看成遍历一个数组。

2 爬楼梯

. - 力扣(LeetCode)

爬楼梯也是一个经典的斐波那契类型的题目。

根据题目意思,每次可以爬 1个 或者 2个 台阶。 

那么如果我们需要爬到第 i 阶太极,我们就只有两种方式可以到达,

1  从第 i-1 阶台阶爬一个台阶到达

2  从第 i-2 阶台阶爬两个台阶到达

那么 dp[i] 不就是 dp[i-1]+dp[i-2] 吗? 他的递推关系都和斐波那契数一样。

同时,要求爬到第 i 个台阶的方法,我们需要知道爬到 i -1 和 i - 2 的方法,那么填表顺序也是从前往后依次填表。

那么这个题我们就直接写代码了:

class Solution {
public:
    int climbStairs(int n) {
        //常规dp
        // vector<int> dp(n+1);
        // dp[1] = 1 ;  //爬到第一个台阶只有一种方法
        // dp[0] = 1 ; //这一步其实单纯是为了方便填 dp[2] ,实际上dp[0]无意义
        // if(n == 1) return 1; 
        // for(int i = 2; i <= n ;++i)
        // dp[i] = dp[i-1] + dp[i-2];
        // return dp[n];
        
        //滚动数组
        int a = 1 , b = 1 , c = 2; //dp[0]  dp[1]  dp[2] 
        if(n==1) return b;
        for(int i = 3 ; i <= n ; ++ i)
        {
            a=b;
            b=c;
            c=a+b;
        } 
        return c;
    }
};

3 三步问题

. - 力扣(LeetCode)

首先看题目要求:这个题目无非就是比前一个爬楼梯的题目多了一种情况,就是可以通过 i-3 一次爬上3阶台阶到达 i 。

如果我们使用上面的状态表示:

 dp[i]  表示从第 0 级台阶走到第 i 级台阶的方法数

那么 dp[i] = dp[i-1] + dp[i-2] + dp[i-3]                  i > 3

那么我们需要初始化 dp[1] 和 dp[2] ,以及为 dp[0]设置一个初始值不影响dp[3]。

首先dp[1] = 1 ,因为从0 到1 只有一种方法就是一次跳一步。 而dp[1] = 2 ,有两种方法,一种是从 0 跳两个台阶,一个是从1跳一个台阶。

而 dp[3] = dp[0] + dp[1] + dp[2]  ,而显然,第三个台阶可以有第二个台阶跳一次一步到达,也可以由第 1 个台阶一次跳两步过来,也可以由第0个台阶一次跳三步到达,那么我们最好是将dp[0]初始化为1 ,满足dp[3] = dp[1] + dp[2] + 1

那么我们的代码如下:

class Solution {
public:
    int waysToStep(int n) {
        //dp[i]表示从第 0 个台阶到达第 i 个的台阶的方法数
        //dp[i] = dp[i-1] + dp[i-2] + dp[i-3]   i>3
        vector<int> dp(n+1);
        dp[0] = 1 ; //无实际意义,为了填 dp[3] 时不需要特殊处理
        dp[1] = 1;
        if(n==1) return 1;  //边界情况
        dp[2] = 2;
        int MOD = 1e9+7;    //模数

        for(int i = 3 ; i <= n;++i)     //从3开始填
        { 
            dp[i] = (dp[i-1] + dp[i-2])%MOD + dp[i-3];      //三个数相加的时候可能已经超出int范围(21亿)了
            dp[i]%=MOD;
        }
        return dp[n];
    }   
};

同时,我们也可以换一种状态表示方法来完成这个题目的动态规划

我们可以用 dp[i] 表示从第 i 个台阶走到第 n 个台阶的方法数。

假设我们目前在第 i 个台阶,那么我们下一步只能走到 i+1,i+2,i+3 这三个中的一个,那么从第 i 个台阶走到第 n 个台阶的方法数我们就可以先从 i 走到 i+1 , 从 i 走到 i+2 ,从 i 走到i+3 ,那么问题就转换为了,从 i+1 ,i+2 .i+3 走到第n个台阶的方法数的总和就是从 i 走到第 n 个台阶的方法数。

而从第 i+1 个台阶走到第n个台阶的方法数就是 dp[i+1] ,依次类推

dp[i] = dp[i+1] + dp[i+2] + dp[i+3]       i > 0 && i <= n - 3

那么这种状态表示下,我们的填表顺序就应该是从后往前依次填表了。

那么怎么初始化呢? dp[n-1] = 1 和 dp[n-2] = 2 这两个还是一样的,现在的问题就是 dp[n] 怎么填?如果从逻辑上来说,dp[n]本来就在第n个台阶,那么他就应该填 0 ,但是我们为了方便填 dp[n-3] ,我们可以把 dp[n] 初始化为 1 ,这样 dp[n-3] 就直接用递推公式来求了。

同时编写代码的过程中我们也要考虑特殊情况, n 的范围是  n>=1 ,当n =1时,dp[n-2]会越界。

题目要求返回的是从第 0 个台阶走到第 n 个台阶的方法数,那么我们需要返回 dp[0]

        //dp[i] 表示从第 i 个台阶走到第n个台阶的方法总数
        vector<int> dp(n+1);
        dp[n] = 1 ; //没有实际意义,为了填 dp[n-3] 的准确性
        dp[n-1] = 1;
        // n = 1 时,dp[n-2]越界
        if(n==1) return 1;
        dp[n-2] = 2;
        int MOD = 1e9+7;
        for(int i = n-3 ; i>=0 ; --i)
        {
            dp[i] = (dp[i+1] + dp[i+2])%MOD+dp[i+3];
            dp[i] %= MOD;
        }
        //返回从第 0 个台阶到第 n 个台阶的方法总数
        return dp[0];

4 使用最小花费爬楼梯

LCR 088. 使用最小花费爬楼梯 - 力扣(LeetCode)

首先还是解析题意,假设cost中有 n 个数据,那么对应的就是第0 ,1 .... n-1 级台阶,而爬到楼顶的意思是爬完所有楼梯,那么我们可以把楼顶视为第 n 个台阶,我们需要求出爬到第n级台阶的最小花费,每次从当前的第 i 个台阶向上爬,需要支付 cost[i] 个体力,支付体力后,可以选择向上爬一个台阶或者两个台阶。

那么对于第 i 个台阶,可以从第 i-1 级台阶,支付cost[i-1] ,一步爬上来。  也可以由第 i-2 级台阶,支付cost[i-2] ,两步爬上来。 而题目要求的是爬上第 n 级台阶的最小花费,那么我们需要选择这两种方法中花费较少,来爬上第 i 级台阶。 

那么来设计我们的dp。 

状态表示:dp[i] 表示爬到第 i 级台阶所需要的最少花费。

题目中表明可以选择第 0 级或者第 1 级台阶作为起点,那么我们可以初始化 dp[0] = dp[1] = 0 

根据我们前面的推到思路,我们能够得出一下状态转移方程

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

由状态转移方程可以知道,填写dp[i] 需要用到 dp[i-1] 和dp[i-2] ,那么我们的填表顺序还是从左往右填。

而最终的返回值就是爬到第 n 个台阶的最小花费,那么就是 dp[n];

代码如下:

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        //dp[i] 表示从起点(第0或者第1级台阶)爬到第i级台阶的最小花费
        int dp[n+1];
        //初始化
        dp[0] = dp[1] = 0 ;
        //填表
        for(int i = 2 ; i <= n ; ++ i)
        {
            dp[i] = min(dp[i-1] + cost[i-1] , dp[i-2] + cost[i-2]);
        }
        return dp[n];
    }
};

我们也可以参考上一个题目的做法,换一种dp思路。

我们可以定义 dp[i] 为从第 i 级台阶爬到第 n 级台阶所需要的最小花费。那么从第 i 级台阶出发,我么可以支付 cost [i] 一次爬到第 i+1 级台阶 或者 第 i+2 级台阶,那么需要取他们的较小值,于是我们能够得出一下的状态转移方程:

dp[i] = cost[i] + min(dp[i+1] , dp[i+2])      0 <= i <= n - 2

而对于边界条件,dp[n]  = 0 , 第n级台阶到本身就在终点,不需要任何花费。而对于dp[n-1] ,只有一种方式爬到第 n 级台阶,那么就是支付 cost[i] 爬一步到 n ,那么dp[n-1] = cost[n-1]; 

那么这种 dp 思路的填表顺序就是从右往左。

而我们的返回值自然就是 dp[0] 和 dp[1] 中的较小值。

        int n = cost.size();
        //dp[i] 表示从第 i 级台阶爬到楼顶(第n级台阶)所需的最小花费
        int dp[n+1];
        //初始化
        dp[n] = 0 ; 
        dp[n-1] = cost[n-1];
        for(int i = n -2 ; i >=0 ; -- i)
        {
            dp[i] = cost[i] + min(dp[i+1] , dp[i+2]);
        }
        return min(dp[0],dp[1]);

5 解码方法

91. 解码方法 - 力扣(LeetCode)

题目的意思就是: 我们有一种编码规则,字符 1  映射 字母 A ,字符 2 映射 字母 B, 依次类推,"26" 映射字母 Z 。现在题目给我们一个数字字符串,要我们求出由此字符串能解码的方法总数。

为什么同样一个字符串有不同的解码方式呢?题目中也告诉了我们,对于在 26 以内的数字,比如23 ,我们可以分成两部分" 2 " 和 " 3 "来解码,对应 "B" 和 "C" ,也可以直接解码为一个字符 "W"。但是有一个特殊的数字 0 ,我们只可以将 0 视为后置 0 ,比如 10 ,20 ,而不能将其解析为前置 0 ,比如 01 ,02  。

那么对于字符串中的一个数字字符,我们可以有三种解码方法 

1 单独解码  ;  2 和他前面的数字合起来进行解码  ; 3  和他后面的数字合起来进行解码)

当然这里的方法3 其实和方法2重叠了,后面计算的时候我们只需要关注其中一个

但是对于数字字符 0 而言,他只有一种解码方式就是和他前面的数字进行解码。 

而如果遇上连续的字符 0 ,那么这个数字字符串就是无法解码的,因为两个连续的 0 中的后一个无论如何都无法进行解码。这就是题目中所说的无法解码的情况。如果字符串的第一个字符就是 0,那么也是无法解码的。还有就是如果最后一个字符是 0 ,且他无法与前一个字符结合为一个合法的解码方式,那么这也是无法解码的。

那么这道题我们使用动态规划来做的话也很简单。

首先确定状态表示,我们可以这样表示 :

dp[i] 表示以字符串第 i 个位置(s[i])结尾时,解码方法的总数。

那么对于第 i 个位置的字符,有两种解码方式 1 单独解码 , 2 和前面的字符进行解码,,如果 第 i 个字符选择单独解码,dp[i] = dp[i-1]  。 如果第 i 个字符选择与第 i-1 个字符结合解码,那么第  i-1 个字符就不能先选择解码方式,dp[i] = dp[i-2] ;

那么 dp[i] 的结果就是这两种解码方法的总数。

那么我们由以下状态转移方程:

dp[ i ] = 0;

如果第 i 个字符能够单独解码, dp[ i ] += dp[i-1]

如果第 i 个字符能够与前一个字符结合解码,dp[i] += dp[i-2]

由于状态转移方程中需要用到dp[i-2],那么要求  i >= 2 才能在循环中自动填表,所以初始化的话,我们需要初始化一下 dp[0] 和 dp[1], 如果s[0] 为‘0’ 那么dp[0] = 0 , 否则dp[0] =1;而对于dp[1] ,则需要按照上面的思路来求得。

那么最终返回的就是 dp[n-1] 。

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size(); 
        //dp[i]表示以s[i]结尾时,解码的总数
        vector<int> dp(n);
        //初始化
        dp[0] = s[0] != '0' ; //如果s[0] =='0',那么dp[0] = 0 ,否则dp[0] = 1
        if(n == 1) return dp[0];
        if(s[1] != '0') dp[1] += dp[0]; //说明dp[1]可以单独解码
        int sum = (s[0]-'0')*10 + s[1]-'0'; //s[1] 与 s[0] 结合解码
        if(sum >= 10 && sum <= 26)  dp[1] ++;
        
        for(int i = 2 ; i < n ; ++ i)
        {
            if(s[i] != '0') dp[i] += dp[i-1];
            sum = (s[i-1] -'0')*10 + s[i]-'0';
            if(sum >= 10 && sum <= 26) dp[i] += dp[i-2];
        }
        return dp[n-1];
    }
};

但是在代码中我们发现了,对于 dp[1] 的初始化,其实逻辑上是和我们的状态方程差不多,唯一不同的就是第二种编码方式的 dp[i] +=dp[i-2]  便成了 dp[i]++ ,那么我们是否可以针对dp[1] 虚拟出一个 dp[i-2] = 1 ,那么这样一来 dp[1] 也能直接在循环中自动填表,而不需要我们手动初始化呢?

自然是可以的,我们只需要在 dp 表中多开一个空间,第一个位置也就是 dp[0] 作为为了方便 dp[1]填表而增加的虚拟空间。 那么对应的,我们的状态表示其实就发生了变化,dp[i]便表示为以s[i-1]为结尾,编码方法的总数。

在很多的动态规划的问题中,都会有这样的问题,就是处理边界问题的时候,有时候多开一个或者一行或者多个空间之后,可以无需手动初始化某些边界,可以在填表过程中通过通用的状态表示方程来填入。

但是这些虚拟位置的值我们是需要具体情况具体分析的。

      //多开一个虚拟空间
        int n = s.size();
        vector<int> dp(n+1);
        dp[0] = 1;
        dp[1] = s[0]!='0';
        for(int i = 2; i <= n ; ++i)
        {
            //注意新表的dp[i]对应的是s[i-1]
            if(s[i-1] !='0') dp[i] += dp[i-1];
            int sum = (s[i-2]-'0')*10 + s[i-1] -'0';
            if(sum >= 10 && sum <= 26) dp[i] += dp[i-2];
        }
        return dp[n];

总结

通过几个简单的动态规划的例题,我们大概能够理解动态规划的思路和做题流程,在动态规划问题中,最难想到的其实并不是状态转移方程,而是最基本的状态表示,很多时候,只要我们能够思考出一个巧妙的状态表示,那么状态转移方程也就信手拈来了,在很多时候,我们都能够凭借以往的经验,确定状态表示为: 以... 为起点或者以 ... 为终点,xxxx这样的形式。当然我们上面所讲解的都是最基础入门的动态规划的题目,后续还会更新其他类型的动态规划问题

的做题思路已经步骤。

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

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

相关文章

《Python实战进阶》No16: Plotly 交互式图表制作指南

No16: Plotly 交互式图表制作指南 Plotly是一款用来做数据分析和可视化的在线平台&#xff0c;功能真的是非常强大&#xff0c;它主要有以下特点&#xff1a; 图形多样化&#xff1a;在线绘制多种图形&#xff0c;比如柱状图、饼图、直方图、饼图、气泡图、桑基图、股票图、旭…

代码随想录算法训练营第22天 | 组合总和 分割回文串

39. 组合总和 39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 题目链接/文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;带你学透回溯算法-组合总和&#xff08;对应「leetcode」力扣题目&#xff1a;39.组合总和&#xff09;| 回溯法精讲&#xff01;_哔哩哔哩_…

DeepSeek 医疗大模型微调实战讨论版(第一部分)

DeepSeek医疗大模型微调实战指南第一部分 DeepSeek 作为一款具有独特优势的大模型,在医疗领域展现出了巨大的应用潜力。它采用了先进的混合专家架构(MoE),能够根据输入数据的特性选择性激活部分专家,避免了不必要的计算,极大地提高了计算效率和模型精度 。这种架构使得 …

lvgl在ubuntu中模拟运行

文章目录 前言具体的步骤 前言 lvgl是一个图像UI的开源框架&#xff0c;用于嵌入式的设备之中。 在学习lvgl时&#xff0c;我们最好是现在PC上模拟运行&#xff0c;所以我们学习lvgl的第一步可以说是在我们的电脑上搭建模拟的运行环境。 参考官方的操作 lvgl在ubuntu上模拟运…

Unity引擎使用HybridCLR(华佗)热更新

大家好&#xff0c;我是阿赵。   阿赵我做手机游戏已经有十几年时间了。记得刚开始从做页游的公司转到去做手游的公司&#xff0c;在面试的时候很重要的一个点&#xff0c;就是会不会用Lua。使用Lua的原因很简单&#xff0c;就是为了热更新。   热更新游戏内容很重要。如果…

Vue项目通过内嵌iframe访问另一个vue页面,获取token适配后端鉴权(以内嵌若依项目举例)

1. 改造子Vue项目进行适配(ruoyi举例) (1) 在路由文件添加需要被外链的vue页面配置 // 若依项目的话是 router/index.js文件 {path: /contrast,component: () > import(/views/contrast/index),hidden: true },(2) 开放白名单 // 若依项目的话是 permission.js 文件 cons…

vue3 vite项目安装eslint

npm install eslint -D 安装eslint库 npx eslint --init 初始化配置&#xff0c;按项目实际情况选 自动生成eslint.config.js&#xff0c;可以添加自定义rules 安装ESLint插件 此时打开vue文件就会标红有问题的位置 安装prettier npm install prettier eslint-config-pr…

Excel·VBA江西省预算一体化工资表一键处理

每月制作工资表导出为Excel后都需要调整格式&#xff0c;删除0数据的列、对工资表项目进行排序、打印设置等等&#xff0c;有些单位还分有“行政”、“事业”2个工资表就需要操作2次。显然&#xff0c;这种重复操作的问题&#xff0c;可以使用VBA代码解决 目录 代码使用说明1&a…

【A2DP】SBC 编解码器互操作性要求详解

目录 一、SBC编解码器互操作性概述 二、编解码器特定信息元素(Codec Specific Information Elements) 2.1 采样频率(Sampling Frequency) 2.2 声道模式(Channel Mode) 2.3 块长度(Block Length) 2.4 子带数量(Subbands) 2.5 分配方法(Allocation Method) 2…

R软件线性模型与lmer混合效应模型对生态学龙类智力测试数据层级结构应用

全文链接&#xff1a;https://tecdat.cn/?p40925 在生态与生物学研究中&#xff0c;数据常呈现复杂结构特征。例如不同种群、采样点或时间序列的观测数据间往往存在相关性&#xff08;点击文末“阅读原文”获取完整代码、数据、文档&#xff09;。 传统线性模型在处理这类非独…

打造智能聊天体验:前端集成 DeepSeek AI 助你快速上手

DeepSeek AI 聊天助手集成指南 先看完整效果&#xff1a; PixPin_2025-02-19_09-15-59 效果图&#xff1a; 目录 项目概述功能特点环境准备项目结构组件详解 ChatContainerChatInputMessageBubbleTypeWriter 核心代码示例使用指南常见问题 项目概述 基于 Vue 3 TypeScrip…

C语言-语法

数据类型 字符串 C中字符串拼接不用+号,直接使用空格。 char* str = "hello" "world"; 换行链接,加上\就不会报错 char* longStr = "00000000000000000000000000000\ 00000000000000000000000000000"; typedef C 语言提供了 typedef …

Unity组件TrailRenderer屏幕滑动拖尾

Unity组件TrailRenderer屏幕滑动拖尾 介绍制作总结 介绍 今天要做一个拖动效果&#xff0c;正好用到了TrailRenderer这个组件&#xff0c;正好分享一下 效果参考如下&#xff1a; 制作 1.创建空物体TrailObject添加组件TrailRenderer 下面的材质可以根据自己想要制作的效果去…

大模型信息整理

1. Benchmarks Reasoning, conversation, Q&A benchmarks HellaSwagBIG-Bench HardSQuADIFEvalMuSRMMLU-PROMT-BenchDomain-specific benchmarks GPQAMedQAPubMedQAMath benchmarks GSM8KMATHMathEvalSecurity-related benchmarks PyRITPurple Llama CyberSecEval2. 国内外…

【Tools】Windows下Git 2.48安装教程详解

00. 目录 文章目录 00. 目录01. Git简介02. Git参考资料03. Git安装04. Git测试05. 附录 01. Git简介 Git(读音为/gɪt/。)是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。 [1] Git 是 Linus Torvalds 为了帮助管理 Linux 内核…

护网中shiro常问的问题

1. 漏洞原理 Apache Shiro 是一个强大的 Java 安全框架&#xff0c;提供身份验证、授权、加密及会话管理功能。Shiro 使用 rememberMe 机制来存储用户会话信息&#xff0c;该机制依赖于加密后的 Cookie。当攻击者能够控制 Cookie 并且服务器使用了不安全的反序列化机制时&…

swift -(5) 汇编分析结构体、类的内存布局

一、结构体 在 Swift 标准库中&#xff0c;绝大多数的公开类型都是结构体&#xff0c;而枚举和类只占很小一部分 比如Bool、 Int、 Double、 String、 Array、 Dictionary等常见类型都是结构体 ① struct Date { ② var year: Int ③ var month: Int ④ …

软件工程笔记下

从程序到软件☆ 章节 知识点 概论☆ 软件的定义&#xff0c;特点&#xff0c;生存周期。软件工程的概论。软件危机。 1.☆软件&#xff1a;软件程序数据文档 &#xff08;1&#xff09;软件&#xff1a;是指在计算机系统的支持下&#xff0c;能够完成特定功能与性能的包括…

【项目日记(九)】细节优化与对比测试

前言 上面我们对申请和释放的过程都已写完&#xff0c;并进行了单线程的联调。本期我们来对一些细节进行优化以及与malloc 进行对比测试。 目录 前言 一、大于256KB的内存申请问题 • 申请过程 • 释放过程 • 简单测试 二、使用定长内存池脱离使用new 三、优化释放对…

PyTorch系列教程:编写高效模型训练流程

当使用PyTorch开发机器学习模型时&#xff0c;建立一个有效的训练循环是至关重要的。这个过程包括组织和执行对数据、参数和计算资源的操作序列。让我们深入了解关键组件&#xff0c;并演示如何构建一个精细的训练循环流程&#xff0c;有效地处理数据处理&#xff0c;向前和向后…