C++算法 —— 动态规划(5) 子序列

news2024/11/20 8:28:43

文章目录

  • 1、动规思路简介
  • 2、最长递增子序列
  • 3、摆动序列
  • 4、最长递增子序列的个数
  • 5、最长数对链
  • 6、最长定差子序列
  • 7、最长斐波那契子序列的长度
  • 8、最长等差数列
  • 9、等差数列划分 II


每一种算法都最好看完第一篇再去找要看的博客,因为这样会帮你梳理好思路,看接下来的博客也就更轻松了。当然,我也会尽量在写每一篇时都可以不懂这个算法的人也能边看边理解。

1、动规思路简介

动规的思路有五个步骤,且最好画图来理解细节,不要怕麻烦。当你开始画图,仔细阅读题时,学习中的沉浸感就体验到了。

状态表示
状态转移方程
初始化
填表顺序
返回值

动规一般会先创建一个数组,名字为dp,这个数组也叫dp表。通过一些操作,把dp表填满,其中一个值就是答案。dp数组的每一个元素都表明一种状态,我们的第一步就是先确定状态。

状态的确定可能通过题目要求来得知,可能通过经验 + 题目要求来得知,可能在分析过程中,发现的重复子问题来确定状态。还有别的方法来确定状态,但都大同小异,明白了动规,这些思路也会随之产生。状态的确定就是打算让dp[i]表示什么,这是最重要的一步。状态表示通常用某个位置为结尾或者起点来确定,这点在下面的题解中慢慢领会。

状态转移方程,就是dp[i]等于什么,状态转移方程就是什么。像斐波那契数列,dp[i] = dp[i - 1] + dp[i - 2]。这是最难的一步。一开始,可能状态表示不正确,但不要紧,大胆制定状态,如果没法推出转移方程,没法得到结果,那这个状态表示就是错误的。所以状态表示和状态转移方程是相辅相成的,可以帮你检查自己的思路。

要确定方程,就从最近的一步来划分问题。

初始化,就是要填表,保证其不越界。像第一段所说,动规就是要填表。比如斐波那契数列,如果要填dp[1],那么我们可能需要dp[0]和dp[-1],这就出现越界了,所以为了防止越界,一开始就固定好前两个值,那么第三个值就是前两个值之和,也不会出现越界。初始化的方式不止这一点,有些问题,假使一个位置是由前面2个位置得到的,我们初始化最一开始两个位置,然后写代码,会发现不够高效,这时候就需要设置一个虚拟节点,一维数组的话就是在数组0位置处左边再填一个位置,整个dp数组的元素个数也+1,让原先的dp[0]变为现在的dp[1],二维数组则是要填一列和一行,设置好这一行一列的所有值,原先数组的第一列第一行就可以通过新填的来初始化,这个初始化方法在下面的题解中慢慢领会。

第二种初始化方法的注意事项就是如何初始化虚拟节点的数值来保证填表的结果是正确的,以及新表和旧表的映射关系的维护,也就是下标的变化。

填表顺序。填当前状态的时候,所需要的状态应当已经计算过了。还是斐波那契数列,填dp[4]的时候,dp[3]和dp[2]应当都已经计算好了,那么dp[4]也就出来了,此时的顺序就是从左到右。还有别的顺序,要依据前面的分析来决定。

返回值,要看题目要求。

子序列问题,因为序列的元素可以在原数组中不连续,所以i位置的元素也可以和i - 4位置的元素连接,所以把i之前的所有位置都设为j,j被表示为很多个值,比如i - 1,i - 2,主要用作分析。

第一题是一个被当做模板的题,有关子序列的题的分析思路都可以在这道题里找到源头。这篇博客除了这道题外,如果要看其它的题,都建议先看完第一题。

2、最长递增子序列

300. 最长递增子序列

在这里插入图片描述

子序列不同于子数组,虽然里面的元素都是从左往右的顺序,但子序列里的元素在原数组中可以不连续,也就是原数组abcd,acd就是一个子序列,da就不是子序列,因为相对顺序和原数组不对应,而acd不能是子数组,abc是子数组,所以子序列其实包含子数组。

严格递增意思就是没有重复的元素,只有递增的趋势,不能有相等的元素。

先确定状态,惯用这个写法,dp[i]表示为以i位置元素结尾的所有子序列中,最长递增子序列的长度。可以只用当前位置的元素来构成一个子序列,也可以把当前元素前面的若干个元素拼接起来做子序列,也就是长度大于1和长度为1的子序列。当前位置i可以和i - 1,i - 2,i - 3等位置的元素拼接,我们暂且把i之前的位置称作j,也就是说j <= i,且无论j是多少,nums[j]小于nums[i]才能组成子序列,如果可以组成,那么当前位置i存储的长度就应当是dp[j] + 1,但dp[j]也应当找出最大值,也就是从0到j位置存的最大值,再去+1。所以这里就得是N ^ 2的时间复杂度。如果只取当前元素作为子序列,那么就存1。所以方程就出来。

初始化,通过上面的分析可以发现,两个方程1和max(dp[j] + 1)最低都是1,所以我们就初始化dp表为1,这样长度为1的情况就不需要考虑了。填表顺序是从左到右,返回值是dp表中最大值,子序列可能是任意一个位置有最长长度,所以不能只取最后一个值,而是dp表所有值取最大。

这道题用动规并不是优秀的解法,后面的算法博客会写到这题更好的解法。

    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n, 1);
        int ret = 1;
        for(int i = 1; i < n; i++)
        {
            for(int j = 0; j < i; j++)
            {
                if(nums[j] < nums[i])
                {
                    dp[i] = max(dp[j] + 1, dp[i]);
                }
            }
            ret = max(ret, dp[i]);
        }
        return ret; 
    }

3、摆动序列

376. 摆动序列

在这里插入图片描述

摆动序列的意思就是一些数字,从左到右,每两个数之间的差应当是一正一负,并且差值不能是0,仅有一个元素也可以是摆动序列。

确定状态。其实如果看了上一个博客的话,会发现它和等差数列划分这道题很像,但也有所不同。我们定dp[i]是以i位置元素为结尾的所有子序列中,最长的摆动序列的长度,因为题目要求返回最长长度。但这个状态能解决问题吗?摆动序列需要看前一个差值是负数还是正数,然后当前位置和前一个位置的差值就对应着,是正数或者负数,才能确定到i位置时,是否能构成摆动序列,所以状态应当分成两种,f和g表。差值是负数,意思就是前一个值比后一个值大,那么从前一个值到后一个值就是在下降,如果比后一个值小,就是在上升。所以我们定义g[i]是以i位置元素结尾的所有子序列中,最后一个位置呈现下降趋势的最长摆动序列的长度,因为题目要求返回最长长度,f和g表就是对这个状态再做区分,而f表则表示上升趋势。

和上一个题一样,i位置的元素可能和i - 1或者i - 2,i - 3位置的元素连接构成一个子序列,我们就把前面这几个值都设置为j,j可表示为不同的值。仅有一个元素,就是长度为1的序列,那么f[i]就是1;不是一个元素,和之前的元素连接,也就是长度不为1的序列,而f[i]表示的到i位置时呈现上升趋势,也就是i 比 i - 1位置的元素大,那如果想达到这个效果,i - 2到i - 1就得是下降趋势,下降趋势正好就是g[i - 1],然后再 + 1即可。不过应当是g[j] + 1,从0 到 j最长的序列再 + 1。g表也是如此,要么是1,要么是f[j] + 1。

初始化,因为一个元素时应当填1,所以我们不如把整个表都初始化为1,这样就不用考虑长度为1的情况了。填表顺序是从左到右,两个表一起填。返回值是f和g表中的最大值。

    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        vector<int> g(n, 1);
        auto f = g;
        int ret = 1;
        for(int i = 1; i < n; i++)
        {
            for(int j = 0; j < i; j++)
            {
                if(nums[j] < nums[i]) f[i] = max(g[j] + 1, f[i]);
                else if(nums[j] > nums[i]) g[i] = max(f[j] + 1, g[i]);
            }
            ret = max(max(g[i], f[i]), ret);
        }
        return ret;
    }

4、最长递增子序列的个数

673. 最长递增子序列的个数

在这里插入图片描述

看这道题之前先看最长递增子序列,以下的题解也会建立在那道题基础之上。

之前的题返回长度,这次返回个数。解决这个问题之前我们先看一个问题,如果用一次遍历来解决在数组中找出最大值出现的次数这个问题?这里的解决办法就是先取第一个数为最大数,让计数初始化为1,如果找到一个最大数小于的数字,那么什么都不做;如果等于这个数,那就计数 + 1;如果大于这个最大值,那么就更换最大值,然后计数再赋值为1。

回头再看这道题,我们要找最长子序列的个数。让dp[i]表示以i位置元素为结尾的所有的子序列中,最长子序列的个数。但我们应当知道何为最长?也就是最长子序列的长度,才能判断是否更新最长子序列,以及我们还得有个计数。所以得有两个dp表,一个表示以i位置元素为结尾的所有的子序列中,最长递增子序列的长度len[i],一个表示个数count[i]。将i前面的位置设置为j,j可以表示成i - 1, i - 2等,len[i] = len[j] + 1这个等式一定成立,但len[j]必须得是从0到j这个位置的最大值才行。如果len[j] + 1等于len[i],也就是长度没变,还是这个长度,那么count[i]就只是count[j] + 1,多一个;如果是大于len[i],那么最长的子序列应当以某一个j位置元素结尾的序列,那么此时count不需要变,len[i]赋值为len[j] + 1;如果是小于,那就还是之前j位置的count,len[i]的值就还是len[i]的值。

通过以上的分析,我们可以把len和count全部初始化为1。填表顺序是从左到右,两个表一起填,返回值就是count表中的最大值。

看代码来理解整体思路。

    int findNumberOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> len(n, 1), count(n, 1);
        int retlen = 1, retcount = 1;
        for(int i = 1; i < n; i++)
        {
            for(int j = 0; j < i; j++)
            {
                if(nums[j] < nums[i])
                {
                    if(len[j] + 1 == len[i]) count[i] += count[j];
                    else if(len[j] + 1 > len[i])
                    {
                        len[i] = len[j] + 1;
                        count[i] = count[j];
                    }
                }
            }
            if(retlen == len[i]) retcount += count[i];
            else if(retlen < len[i])
            {
                retlen = len[i];
                retcount = count[i];
            }
        }
        return retcount;
    }

5、最长数对链

646. 最长数对链

在这里插入图片描述

因为b要求小于c,所以不能等于。

确定状态。如果是以i位置为结尾,那么比i小的其实有可能在i左边,也可能在i右边。所以针对原数组我们还得需要处理一下,以每个数对的第一个来排成升序,为什么这么排?两个数对,从左到右分别是abcd,根据题目,a < b,c < d,排序后,a一定<= c,所以a一定 < d,所以a一定不能连接后面的数对,这样就能保证符合条件的数对都在前面。排序后,dp[i]就表示以i位置为结尾的所有数对链中,最长的数对链。i位置的数对可以和前面,不仅仅只是前一个数对连接,我们可以把前面的位置都设为j。j可以取i - 1, i - 2位置的数对,这样就会分成两种情况,长度为1,只有i位置的数对,长度不为1,包含前面的数对链,也就是dp[j] + 1,而dp[j]需要求最大值,才能+1赋值给dp[i],从0到j循环来找最大值。

初始化,因为一个元素也可以形成数对链,所以全部初始化为1,这样也不用考虑长度为1的数对了。填表顺序是从左到右。返回值,最长数对链有可能出现在以某个数对结尾的数对链,所以需要比较dp表所有的值来找到最大值返回。

    int findLongestChain(vector<vector<int>>& pairs) {
        sort(pairs.begin(), pairs.end());
        int n = pairs.size();
        vector<int> dp(n, 1);
        int ret = 1;
        for(int i = 1; i < n; i++)
        {
            for(int j = 0; j < i; j++)
            {
                if(pairs[j][1] < pairs[i][0]) dp[i] = max(dp[j] + 1, dp[i]);
            }
            ret = max(ret, dp[i]);
        }
        return ret;
    }

6、最长定差子序列

1218. 最长定差子序列

在这里插入图片描述

惯用的思路就是dp[i]表示以i位置的元素为结尾的所有的子序列中,最长的等差子序列的长度。
假设当前位置的元素为a,和a相差差值的元素是b,b可能在前面的序列出现不止一次,但为了要最长,我们应当选择最靠近a的那个,b所在的位置设为j,那么dp[i]就是dp[j] + 1。

但是找到最靠近a的b就需要遍历,我们不如把b加上对应的dp表里的值放到哈希表里,这样直接映射到哈希表中去找,更甚一步,我们可以a和dp[i]的值放到哈希表中,这样就不需要dp表,在哈希表中做动态规划。

初始化,因为单个元素也可以,所以哈希表中,hash[arr[0]] = 1,也就是arr的第1元素对应的长度是1。填表顺序是从左到右。返回值是dp表的最大值,也就是哈希表的最大值。

    int longestSubsequence(vector<int>& arr, int difference) {
        unordered_map<int, int> hash;
        hash[arr[0]] = 1;
        int ret = 1;
        for(int i = 1; i <arr.size(); i++)
        {
            hash[arr[i]] = hash[arr[i] - difference] + 1;//对应着dp[i]的方程。dp[i]就是hash[arr[i]]
            //如果b不存在,那么hash[arr[i] - difference]就是0,0+1=1,就是1
            //如何解决最后一个b的问题,在遍历中,同样的b,会映射到同样的位置,重复地覆盖,所以最后留下的就是最靠近a的b的值
            ret = max(ret, hash[arr[i]]);
        }
        return ret;
    }

7、最长斐波那契子序列的长度

873. 最长的斐波那契子序列的长度

在这里插入图片描述

题目意思是,要想形成这个序列,至少是3个数,并且一个值可以由前两个值相加而成,并且没有重复的元素,符合严格递增。

确定状态。惯用的是dp[i]表示以i位置结尾的所有子序列中,最长的斐波那契子序列的长度。在一个序列的元素可以在原数组中不连续,比如2345,235就是斐波那契序列。我们设i位置之前的位置为j,那么除了nums[j]和nums[i]这两个元素,最后一个元素卫士就是i - j,但是有一个问题,j位置不确定,我们无法找到这个斐波那契序列是什么样的,它无法找到j前面的值是什么样的,dp[j]也无法表示,所以这个状态是错误的。一个序列三个元素,假设最后一个是a,第二个是b,那么第一个就是a - b,如果以b结尾,那么所处的序列中,第二个是a - b,第一个就是b - (a - b)。所以通过a和b就可以确定整个长序列中所有的元素,所以我们需要以dp[i][j]来表示以i位置和j位置的元素为结尾的所有子序列中,最长的斐波那契序列的长度。无论是数字本身,还是下标,都可以通过后两个数来找到。

现在我们重新设置一下三个数,从左到右分别是abc,下标是kij。通过bc找a,a = c - b。如果a不存在,那么序列只有2个数,所以填2,因为dp[i][j]表示的是长度,但因为小于3,最终肯定还是按照0处理;如果b < a <c,那么也不符合定义,也设为2;如果a存在且a < b,那么就符合定义,是我们理想的情况,那么dp[j]就是dp[i] + dp[k]。

这个题也有和上一个题一样的困惑,上面的分析中,a的确定是通过b和c来确定的,但遍历时是从左到右的,那么下标该如何确定?以及a可能有多个,我们只需要最近的那个a,如果要遍历找到最近的那个a的话,时间复杂度会到达N ^ 3,所以优化一下,把元素和对应的下标绑定,用哈希表来完成。

初始化,把表里的值都初始化为2。三个值是abc,对应的下标设为kij,dp[i][j]需要用到dp[k][i],在二维数组中,dp[k][i]在dp[i][j]上面,所以我们只需要关心上面的值即可。填表顺序是从上到下。返回值是dp表中的最大值,因为序列最长的可能出现某一个位置,所以不是最后一个值。但有特殊情况,如果原数组只有3个数,124,那么不能构成斐波那契序列,dp表的值都是2,那么就判断一下,如果最大值小于3就返回0,不是就返回最大值。

    int lenLongestFibSubseq(vector<int>& arr) {
        int n = arr.size();
        unordered_map<int, int> hash;
        for(int i = 0; i < n; i++) hash[arr[i]] = i;
        int ret = 2;
        vector<vector<int>> dp(n, vector<int>(n, 2));
        for(int j = 2; j < n; j++)
        {
            for(int i = 1; i < j; i++)
            {
                int a = arr[j] - arr[i];
                if(a < arr[i] && hash.count(a)) dp[i][j] = dp[hash[a]][i] + 1;
                ret = max(ret, dp[i][j]);
            }
        }
        return ret < 3 ? 0 : ret;
    }

8、最长等差数列

1027. 最长等差数列

在这里插入图片描述

确定状态,dp[i]表示以i位置为结尾的等差子序列的最大长度。i位置之前,有可能和i - 1,i - 2位置的元素连接,但是按照这个状态,dp存的是长度,我们并不能判断出dp[i]是否能由dp[i - 2]来获得,因为i - 2位置的值和i的值的差值是否能进入这个等差序列是未知的,所以这个状态不行。三个值分别是abc,b - a = c - b,所以a = 2b - c,通过两个值能找到前面一个等差序列的值,所以我们需要二维数组dp[i][j],新的状态就是dp[i][j]是以i位置和j位置的元素为结尾的所有的子序列的最大长度。

现在我们重新设置一下三个数,从左到右分别是abc,下标分别是kij。通过bc找a,a = 2b - c。如果a不存在,那么序列只有2个数,所以填2,因为dp[i][j]表示的是长度,但因为小于3,最终肯定还是按照0处理;如果b < a <c,那么也不符合定义,也设为2;如果a存在且a < b,那么就符合定义,是我们理想的情况,那么dp[j]就是dp[i] + dp[k]。

这个题也有和上一个题一样的困惑,上面的分析中,a的确定是通过b和c来确定的,但遍历时是从左到右的,那么下标该如何确定?以及a可能有多个,我们只需要最近的那个a,如果要遍历找到最近的那个a的话,时间复杂度会到达N ^ 3,所以优化一下,把元素和对应的下标绑定,用哈希表来完成。但还是不行,因为如果题目数组元素过多的话,创建一个哈希表就需要遍历一次,整体的时间复杂度依然不小。

那再来一个更好的优化,边做动规边保存离它最近元素的下标。先看初始化,把表里的值都初始化为2,之后如果最大值是2那就返回0。三个值是abc,对应的下标设为kij,dp[i][j]需要用到dp[k][i],在二维数组中,dp[k][i]在dp[i][j]上面,所以我们只需要关心上面的值即可。

填表时,第一次循环先固定最后一个数,第二遍循环枚举倒数第二个数;或者先固定倒数第二个数,然后枚举最后一个数。但应该使用第二个办法。三个数,下标分别是kij。如果固定了j,i往做移动找合适的位置,找到后,再去确定k,如果下一次j往后挪了一步,那么i还是需要找合适的位置,然后再确定k,所以不如固定i,k的范围就固定在0 ~ i,而这一次计算完后,i和j往后挪一步,k的范围就大一步,更方便。

返回值是dp表中的最大值。

    int longestArithSeqLength(vector<int>& nums) {
        unordered_map<int, int> hash;
        hash[nums[0]] = 0;//先把0位置的值初始化
        int n = nums.size(), ret = 2;
        vector<vector<int>> dp(n, vector<int>(n, 2));
        for(int i = 1; i < n; i++)
        {
            for(int j = i + 1; j < n; j++)
            {
                int a = 2 * nums[i] - nums[j];
                if(hash.count(a))
                    dp[i][j] = dp[hash[a]][i] + 1;
                ret = max(dp[i][j], ret);
            }
            hash[nums[i]] = i;
        }
        return ret;
    }

9、等差数列划分 II

446. 等差数列划分 II - 子序列

在这里插入图片描述

有一定要求,必须至少3个元素。

确定状态。这道题状态的分析和第7个,第8个一样,建议看完第8题再来看第9题,以下的题解也基于第8题状态表示的结论。

三个数abc,下标分别是kij,a = 2b - c。a的位置只能在b前面,在b后面或者不存在我们就初始化对应的位置的值为2。a可能有好几个,但因为这题要个数,并不是要最长,所以我们就可以加上所有的a。假设前面所有的a的下标为k1 ~ kx,有两种情况,一个是除了kx,i,j外,kx前还有若干个符合等差序列的数字,这些情况的个数放在了dp[kx][i],也就是下标kx和i对应元素为结尾的等差序列,后面加上个j就是了,个数不变;另一个就是只有kx,i,j下标对应的元素的3个数字的等差序列,也就是kx前没有数字,这时候就是又加上了一个情况,所以dp[i][j] = dp[kx][i] + 1。kx不只有一个,所以找到一个dp[kx][i],就dp[i][j] = dp[kx][i] + 1。这里还要考虑时间复杂度问题,因为找到每一个a还需要多遍历,更耗费时间,所以我们就把元素和下标就放在哈希表中。

初始化时,因为要拿2个元素来表示状态,那这个序列就初始化为2,这样如果整个数组都不满足,dp表里的值都是2,那么最大值就是2,然后直接返回0就行。有个优化和第8题一样,为了更方便地找kx,我们固定倒数第1个数,移动倒数第2个数。返回值就是dp表的总和。

    int numberOfArithmeticSlices(vector<int>& nums) {
        int n = nums.size();
        unordered_map<long long, vector<int>> hash;
        for(int i = 0; i < n; i++) hash[nums[i]].push_back(i);
        vector<vector<int>> dp(n, vector<int>(n));
        int sum = 0;
        for(int j = 2; j < n; j++)
        {
            for(int i = 1; i <j; i++)
            {
                long long a = (long long)2 * nums[i] - nums[j];
                if(hash.count(a))
                {
                    for(auto k : hash[a])
                    {
                        if(k < i) dp[i][j] += dp[k][i] + 1;
                    }
                }
                sum += dp[i][j];
            }
        }
        return sum;
    }

结束。

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

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

相关文章

Python 图形化界面基础篇:添加标签( Label )到 Tkinter 窗口

Python 图形化界面基础篇&#xff1a;添加标签&#xff08; Label &#xff09;到 Tkinter 窗口 引言什么是 Tkinter 标签&#xff08; Label &#xff09;&#xff1f;步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建标签&#x…

kafka管理工具之kafka-ui的环境搭建笔记

由于项目需要kafka支持认证功能&#xff0c;就把kafka升级到3.2.0了。之前一直使用的kafka tools(现在叫Offset Explorer&#xff0c;个人使用免费&#xff0c;商用付费)&#xff0c;开了认证之后就不好用了&#xff0c;卡的很&#xff0c;一点也不丝滑了&#xff0c;于是只好重…

pytorch代码实现之Partial Convolution (PConv卷积)

Partial Convolution (PConv卷积) Partial Convolution (PConv卷积)&#xff0c;有助于提升模型对小目标检测的性能。目前许多研究都集中在减少浮点运算&#xff08;FLOPs&#xff09;的数量上。然而FLOPs的这种减少不一定会带来延迟的类似程度的减少。这主要源于每秒低浮点运…

浅析建筑电气火灾问题和预防方案

安科瑞 华楠 摘要&#xff1a;近几年来随着技术化和信息化的不断发展&#xff0c;电器在建筑中的应用也是越来越广泛&#xff0c;电气也成为人们生活当中的一部分。现如今建筑物设计中都要增加电气线路的设计&#xff0c;几年电气引起的火灾也不在少数。建筑电气在运行的过程中…

OpenCV(三十七):拟合直线、三角形和圆形

1.点集拟合的含义 点集拟合是一种通过拟合函数或曲线来近似描述给定离散数据点的技术,在点集拟合中&#xff0c;可以使用不同的函数或曲线拟合方法来拟合直线、三角形和圆形。 直线拟合&#xff1a;对于给定的二维数据点集合&#xff0c;可以使用最小二乘法来拟合一条直线。 …

springboot之三:原理分析之自动配置condition

导入&#xff1a; SpringBoot是如何知道要创建哪个Bean的&#xff1f;比如Spring Boot是如何知道要创建RedisTemplate的&#xff1f; Condition&#xff1a; Condition是在Spring4.0增加的条件判断功能&#xff0c;通过这个可以实现选择性的创建Bean操作。 自定义条件&…

C++之string::npos应用实例(一百九十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

使用IDEA创建Vue3通过Vite实现工程化

1、创建Vite项目的分步说明 IntelliJ IDEA与Vite构建工具集成&#xff0c;改善了前端开发体验。Vite 由一个开发服务器和一个构建命令组成。构建服务器通过本机 ES 模块提供源文件。生成命令将代码与汇总捆绑在一起&#xff0c;汇总预配置为输出高度优化的静态资产以供生产。In…

AI绘画:Midjourney超详细教程Al表情包超简单制作,内附关键词和变现方式

大家好&#xff0c;本篇文章主要介绍AI绘画完成表情包的制作和变现方式分享。 你还不会AI表情包制作吗&#xff1f;下面我们详细的拆解制作过程。跟着这个教程做出一套属于自己的表情包。 核心工具Midjourney PS&#xff0c;你就可以得到一套自己的专属表情包啦~ 整体制作…

Go 基础15-在init()函数中检查包级变量的初始状态

从程序逻辑结构角度来看&#xff0c;包&#xff08;package&#xff09;是Go程序逻辑封装的基本单元&#xff0c;每个包都可以理解为一个“自治”的、封装良好的、对外部暴露有限接口的基本单元。一个Go程序就是由一组包组成的。 在Go包这一基本单元中分布着常量、包级变量、函…

胡焕庸线,我国东西地级市分布密度分界线

背景 黑河—腾冲线&#xff0c;又名胡焕庸线&#xff0c;是我国人口密度分布的东西近似分界线。今天把地级市坐标分布密度做成热力图&#xff0c;并与胡焕庸线一起展示时&#xff0c;惊奇的发现&#xff0c;胡焕庸线貌似也是我车东西地级市分布密度的分界线。 生成folium地图…

【17 > 分布式接口幂等性】2. Update的幂等性原理解析

一、 根据 唯一业务号去更新 数据的情况 1.1 原理 1.2 操作 1.3 实战 Stage 1&#xff1a;表添加 version 字段 Stage 2&#xff1a;前端 > 版本号放入隐藏域 Stage 3&#xff1a;后台 > 使用版本号作为更新条件 二、更新操作没有唯一业务号&#xff0c;可使用Tok…

FL Studio Producer Edition 21.0.3.3713中文完整破解版功能特点及安装激活教程

FL Studio Producer Edition 21.0.3.3713中文完整破解版是一款由Image Line公司研发几近完美的虚拟音乐工作站,同时也是知名的音乐制作软件。它让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混音盘&#xff0c;先进的创作工具&#xff0c;让你的音乐突破想象力的限制。…

4.2 Ioc容器加载过程-Bean的生命周期深度剖析

Bean生命周期详解 第一步拿到父类BeanFactory子类 第二步&#xff0c;读取配置类 AnnotatedBeanDefinitionReader 用来读取配置类之外和还做了 第一个是解析类的处理器&#xff0c;没有的话我们的配置类就无法解析 总结this()无参构造函数里面实现了【三大步】 实例…

九安监控初始化后恢复案例

九安监控是国内一个十六线小安防品牌&#xff0c;目前CHS零壹视频恢复程序监控版、专业版、高级版是支持这个安防品牌的&#xff0c;不过下边这个案例比较特殊&#xff0c;具体情况如下。 故障存储:希捷4T监控专用硬盘 故障现象: 客户描述是使用了初始化操作&#xff0c;正常…

Linux中防火墙的简单使用方法

目录 前言 一、概念 1、防火墙的分类&#xff1a; 2、防火墙性能 3、硬件防火墙的品牌、软件防火墙的品牌 4、硬件防火墙与软件防火墙比较 二、linux中的防火墙 1、iptables 2.netfilter/iptables功能 3、四表 iptables中表的优先级 4、五链 三、iptables 1、ipt…

YOLO DNF辅助教程完结

课程完结&#xff01;撒花、撒花、撒花 课程完结&#xff01;撒花、撒花、撒花 课程完结&#xff01;撒花、撒花、撒花 ​呕心沥血三个月&#xff0c;《利用人工智能做DNF游戏辅助》系列实战课程已完结&#xff0c;技术路线贯穿串口通信、目标检测、opencv特征匹配等前沿技术…

计算机组装与维护实训室解决方案

一、产业背景 随着信息技术的不断发展&#xff0c;信息化已经深入到社会中各个层面&#xff0c;它能有效地帮助各个行业提高运营及管理效率&#xff0c;进而帮助企业成长。同时&#xff0c;作为企业信息化建设的基础部分&#xff0c;计算机已经融入了我们的生活&#xff0c;并逐…

【算法】一文带你从浅至深入门dp动态规划

文章目录 一、前言二、动态规划理论基础1、基本概念2、动态规划五部曲【✔】3、出错了如何排查&#xff1f; 三、实战演练&#x1f5e1;0x00 斐波那契数0x01 第N个泰波那契数0x02 爬楼梯0x03 三步问题0x04 使用最小花费爬楼梯⭐解法一解法二 0x05 解码方法* 四、总结与提炼 一、…