【十五】【动态规划】115. 不同的子序列、44. 通配符匹配、10. 正则表达式匹配 ,三道题目深度解析

本文主要是介绍【十五】【动态规划】115. 不同的子序列、44. 通配符匹配、10. 正则表达式匹配 ,三道题目深度解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

动态规划

动态规划就像是解决问题的一种策略,它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题,并将每个小问题的解保存起来。这样,当我们需要解决原始问题的时候,我们就可以直接利用已经计算好的小问题的解,而不需要重复计算。

动态规划与数学归纳法思想上十分相似。

数学归纳法:

  1. 基础步骤(base case):首先证明命题在最小的基础情况下成立。通常这是一个较简单的情况,可以直接验证命题是否成立。

  2. 归纳步骤(inductive step):假设命题在某个情况下成立,然后证明在下一个情况下也成立。这个证明可以通过推理推断出结论或使用一些已知的规律来得到。

通过反复迭代归纳步骤,我们可以推导出命题在所有情况下成立的结论。

动态规划:

  1. 状态表示:

  2. 状态转移方程:

  3. 初始化:

  4. 填表顺序:

  5. 返回值:

数学归纳法的基础步骤相当于动态规划中初始化步骤。

数学归纳法的归纳步骤相当于动态规划中推导状态转移方程。

动态规划的思想和数学归纳法思想类似。

在动态规划中,首先得到状态在最小的基础情况下的值,然后通过状态转移方程,得到下一个状态的值,反复迭代,最终得到我们期望的状态下的值。

接下来我们通过三道例题,深入理解动态规划思想,以及实现动态规划的具体步骤。

115. 不同的子序列 - 力扣(LeetCode)

题目解析

状态表示

对于子序列的问题研究,我们通常研究[0,i]这段区域中的子序列的情况,对于两段字符串,研究子序列问题,我们可以分别研究一个字符串中[0,i]的区域,和另一个字符串中[0,j]的区域中的子序列的情况。

结合题目意思,我们可以定义这样的状态表示,定义dp[i][j]表示s中[0,i]区域的所有子序列,与t中[0,j]区域子数组相等的子序列个数。

状态转移方程

对于状态转移方程的推导,通常都是通过最后一个位置的状况进行分类讨论。

对(i,j)状态进行分析,尝试通过其他状态推导出(i,j)位置的状态值。

  1. 如果s[0,i]区域和t[0,j]子数组相等的子序列包括s[i]元素, 此时s[i]==s[j],要在s中[0,i]区域找和t的[0,j]子数组相等的子序列,只需要在s中[0,i-1]区域找和t的[0,j-1]子数组相等的子序列即可,在所有情况后面添加s[i]和t[j],每一种情况构成(s中[0,i]区域的所有子序列,与t中[0,j]区域子数组相等的子序列)的一种情况,此时dp[i][j]=dp[i-1][j-1]。

  2. 如果s[0,i]区域和t[0,j]子数组相等的子序列不包括s[i]元素, 此时dp[i][j]=dp[i-1][j]。

综上所述,状态转移方程为,

 
if(s[i]==t[j])dp[i][j]+=dp[i-1][j-1];
dp[i][j]+=dp[i-1][j];

因为dp[i][j]都是+=进行赋值,所以dp[i][j]一定要有初始值0,所以我们需要将所有位置的状态初始化为0。

初始化

根据状态转移方程,我们知道在推导(i,j)位置的状态时,可能需要用到(i-1,j-1)(i-1,j)位置的状态。

所以在推导蓝色位置的状态时,会发现越界情况,也就是没有前驱使得蓝色位置状态可以推导出来,所以我们需要把这些位置进行初始化。

  1. 初始化蓝色行部分, 此时i=0,j=0、1、...,我们要在[0,i]子序列中找与[0,j]相等的子序列个数。

    1. 如果[0,j]有至少两个元素, 此时dp[i][j]=0。

    2. 如果[0,j]只有一个元素, 此时dp[i][j]=s[0]==t[0]?1:0。

  2. 初始化蓝色列部分, (0,0)位置在初始化蓝色行部分就处理过了,所以我们从(0,1)开始初始化。 此时i=1、2、...,j=0,我们要在[0,i]子序列中找与[0,j]相等的子序列个数。

      所以我们要遍历s数组[1,i]元素,统计和t[0]相等的元素有多少个。这里需要用两个for循环,时间复杂度是O(N^2),且书写起来有些许复杂。我们希望有更简便的初始化过程。

我们在初始化过程中,觉得这个代码写起来比较复杂,我们希望可以简单实现初始化过程,因此我们可以添加虚拟节点,代替原来需要初始化位置。

即多添加一行和一列。

添加虚拟节点有几点注意事项,

  1. 对虚拟节点进行初始化,需要保证推导蓝色位置的状态的准确性。

  2. 注意下标映射的关系。

对虚拟节点进行初始化:

对虚拟节点进行初始化,一般是通过实际意义来初始化。

  1. 初始化蓝色行部分, 此时i=0,j=0、1、...,我们要在[0,i]子序列中找与[0,j]相等的子序列个数。

    1. 如果[0,j]有至少两个元素, 此时dp[i][j]=0。

    2. 如果[0,j]只有一个元素, 此时dp[i][j]=s[0]==t[0]?1:0。

  2. 初始化蓝色列部分, (0,0)位置在初始化蓝色行部分就处理过了,所以我们从(0,1)开始初始化。 此时i=1、2、...,j=0,我们要在[0,i]子序列中找与[0,j]相等的子序列个数。

      1.   所以我们要遍历s数组[1,i]元素,统计和t[0]相等的元素有多少个。

我们先观察状态表示和状态转移方程,

dp[i][j]表示s中[0,i]区域的所有子序列,与t中[0,j]区域子数组相等的子序列个数。

if(s[i]==t[j])dp[i][j]+=dp[i-1][j-1]; dp[i][j]+=dp[i-1][j];

  1. 对蓝色行部分进行分析, 如果[0,j]有至少两个元素,即j>=1,此时dp[i][j]=0,对应状态转移方程,不管s[i]是否等于t[j],dp[i][j]都需要等于0,所以dp[i-1][j-1],dp[i-1][j]都需要初始化为0。 如果[0,j]只有一个元素,当s[i]==s[j]时,dp[i][j]=1,否则dp[i][j]=0,所以dp[i-1][j-1]应该等于1,dp[i-1][j]应该等于0。 即,(0,0)初始化为1,(0,1)、(0,2)...初始化为0。

  2. 对蓝色列部分进行分析, 此时i=1、2、...,j=0,我们要在[0,i]子序列中找与[0,j]相等的子序列个数。 dp[i][j]表示s中[0,i]区域的所有子序列,与t中[0,j]区域子数组相等的子序列个数。 if(s[i]==t[j])dp[i][j]+=dp[i-1][j-1]; dp[i][j]+=dp[i-1][j]; 如果选s[i]元素,此时s[i]==t[j],dp[i][j]+=1。 如果不选s[i]元素,dp[i][j]+=dp[i-1][j]。 对应状态转移方程,dp[i-1][j-1]应该初始化为1,dp[i-1][j]不需要处理。 即(1,0)(1,1)....全部初始化为1。

综上所述,绿色第一列全部初始化为1,第一行除第一个元素,全部初始化为0。再加上在状态转移方程中的分析,紫色位置状态全部初始化为0。

下标映射关系:

dp[i][j]表示s中[0,i]区域的所有子序列,与t中[0,j]区域子数组相等的子序列个数。

因为多添加了一行和一列,所以实际上dp[i][j]表示s中[0,i-1]区域的所有子序列,与t中[0,j-1]区域子数组相等的子序列个数。

有两种解决办法。

  1. 从dp[i][j]找s,t中对应的位置需要下标减1。即用i-1,j-1找到s,t中的对应位置。

  2. 当然也可以在s,t前面添加一个字符作为平衡,这样dp[i][j]就可以直接与s[i]、t[j]元素进行对应。

实际上下标映射关系改变后,相应的状态表示,状态转移方程都需要改变,但我们只需要注意一下下标的映射关系含义即可。

填表顺序

根据状态转移方程,

if(s[i]==t[j])dp[i][j]+=dp[i-1][j-1];dp[i][j]+=dp[i-1][j];

我们知道,推导(i,j)位置的状态时,我们需要用到(i-1,j-1)(i-1,j)的位置。

  1. 固定i改变j, 此时i的变化一定是从小到大,当推导 (i,)位置的状态时,(i-1,)位置的状态已经得出,所以j的变化可以从小到大,也可以从大到小。

  2. 固定j改变i, 此时j的变化一定是从小到大,又因为需要(i-1,j)位置的状态,所以i的变化也要从小到大。

返回值

根据状态表示,

dp[i][j]表示s中[0,i]区域的所有子序列,与t中[0,j]区域子数组相等的子序列个数。

结合题目意思,我们需要s中[0,m]区域的所有子序列,与t中[0,n]区域子数组相等的子序列个数。

又因为下标映射关系,所以我们需要返回dp[m][n]。

代码实现

 
class Solution {
public:int numDistinct(string s, string t) {// 1. 创建 dp 表// 2. 初始化// 3. 填表// 4. 返回值int n = t.size(), m = s.size();vector<vector<double>> dp(m + 1, vector<double>(n + 1));for (int i = 0; i <= m; i++)dp[i][0] = 1; // 初始化for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++) {dp[i][j] += dp[i-1][j];if (s[i - 1] == t[j - 1])dp[i][j] += dp[i - 1][j - 1];}return dp[m][n];}
};

44. 通配符匹配 - 力扣(LeetCode)

题目解析

状态表示

对于两个字符串之间的dp问题,我们一般的思考方式如下:

  1. 选取第一个字符串的[0,i]区间以及第二个字符串的[0,j]区间当成研究对象,结合题目的要求来定义「状态表示」;

  2. 然后根据两个区间上「最后一个位置的字符」,来进行「分类讨论」,从而确定「状态转移方程」。

我们可以根据上面的策略,解决大部分关于两个字符串之间的dp问题。

因此,我们定义状态表示为: dp[i][j]表示:字符串p[0,j]能否匹配字符串 s 的[0,i]。

状态转移方程

根据最后一个位置的具体状况进行分类讨论,

  1. 如果p[j]为一个小写英文字符,

    1. 如果p[j]==s[i], 此时只需要判断p[0,j-1]字符串能否匹配s[0,i-1]字符串,所以此时dp[i][j]=dp[i-1][j-1]。

    2. 如果p[j]!=s[i], 此时无法匹配,所以dp[i][j]=false。

  2. 如果p[j]==‘?’, 此时让p[j]==s[i],只需要判断p[0,j-1]字符串能否匹配s[0,i-1]字符串,所以此时dp[i][j]=dp[i-1][j-1]。

  3. 如果p[j]=='*',

  4.   正常书写代码,只要一种情况可以匹配,那么dp[i][j]就等于true,所以我们要遍历dp[0][j-1]~dp[i][j-1],此时需要花费O(n)的时间,而dp表是二维,最终我们时间复杂度会达到O(n

      ^3),我们希望可以减低时间复杂度,O(n^3)时间复杂度实在是太大了。 我们观察上述推导,可以发现一个很有趣的东西,dp[i][j-1],dp[i-1][j-1],dp[i-2][j-1]...这里横坐标很有规律的减少1,纵坐标不变。我们尝试推导dp[i-1][j],得到 dp[i-1][j]=(dp[i-1][j]||dp[i-2][j-1]||dp[i-3][j-1]||dp[i-4][j-1]....||dp[0][j-1]||j==0),又因为 dp[i][j]=(dp[i][j-1]||dp[i-1][j-1]||dp[i-2][j-1]||dp[i-3][j-1].....||dp[0][j-1]||j==0),

      所以我们可以写成这样,dp[i][j]=(dp[i][j-1]||dp[i-1][j]) 这样用一个表达式就可以解决这一种情况,将O(n)的时间复杂度降低到了O(1)。(注意,dp[i-1][j]和dp[i][j]省略号会一直到dp[0][j-1],所以是严格相等。) 此时dp[i][j]=(dp[i][j-1]||dp[i-1][j])

将上述情况进行合并和简化,我们可以得到状态转移方程,

 
    if (p[j] == '*')dp[i][j] = dp[i][j-1] || dp[i-1][j];elsedp[i][j] =(p[j] == '?' || s[i] == p[j]) && dp[i - 1][j - 1];

初始化

根据状态转移方程,我们知道,想要推导出(i,j)位置的状态需要用到,(i,j-1)(i-1,j)(i-1,j-1)的状态。

当我们在推导蓝色部分的状态时,就会发现越界的情况,也就是没有前驱使得蓝色位置状态可以推导出来,所以我们需要把这些位置进行初始化。

为了简化初始化的过程,我们可以创建虚拟节点,即多添加一行和一列代替原先需要初始化的位置。

此时绿色部分构成原先蓝色部分的前驱,也就是代替了原先蓝色部分,我们现在只需要初始化绿色部分即可。

添加虚拟节点有几点注意事项,

  1. 对虚拟节点进行初始化,需要保证推导蓝色位置的状态的准确性。

  2. 注意下标映射的关系。

对虚拟节点进行初始化:

dp[i][j]表示:字符串p[0,j]能否匹配字符串 s 的[0,i]。

为了统一状态表示,我们可以定义绿色部分含义为空串。

dp[0][0]表示两个空串是否能匹配,此时dp[0][0]=true。

  1. 当i==0,j>0, 表示s是空串,p[0,j]能否匹配,此时如果p[0,j]全是'*',dp[i][j]=true。

  2. 当i>0,j==0, 表示p是空串,s[0,i]能否匹配,此时dp[i][j]=false。

下标映射的关系:

dp[i][j]表示:字符串p[0,j]能否匹配字符串 s 的[0,i]。

因为多添加了一行和一列,所以实际上dp[i][j]表示s中[0,i-1]区域的所有子序列,与p中[0,j-1]区域子数组相等的子序列个数。

有两种解决办法。

  1. 从dp[i][j]找s,p中对应的位置需要下标减1。即用i-1,j-1找到s,p中的对应位置。

  2. 当然也可以在s,p前面添加一个字符作为平衡,这样dp[i][j]就可以直接与s[i]、t[j]元素进行对应。

实际上下标映射关系改变后,相应的状态表示,状态转移方程都需要改变,但我们只需要注意一下下标的映射关系含义即可。

填表顺序

状态转移方程,

 
    if (p[j] == '*')dp[i][j] = dp[i][j-1] || dp[i-1][j];elsedp[i][j] =(p[j] == '?' || s[i] == p[j]) && dp[i - 1][j - 1];

根据状态转移方程,我们知道在推导(i,j)位置状态时,需要用到(i,j-1)(i-1,j)(i-1,j-1)位置的状态。

  1. 固定i,改变j, i的变化需要从小到大,由于要用到(i,j-1)位置的状态,所以j的变化需要从小到大。

  2. 固定j,改变i, j的变化需要从小到大,由于要用到(i-1,j)位置的状态,所以i的变化需要从小到大。

返回值

dp[i][j]表示:字符串p[0,j]能否匹配字符串 s 的[0,i]。

根据状态表示和题目意思,我们需要判断字符串p[0,n]能否匹配字符串 s [0,m]。

又因为下标映射关系,所以我们需要返回dp[m][n]。

代码实现

 
class Solution {
public:bool isMatch(string s, string p) {int m = s.size(), n = p.size();s = " " + s, p = " " + p;vector<vector<bool>> dp(m + 1, vector<bool>(n + 1)); // 1. 创建 dp 表dp[0][0] = true;                                     // 2. 初始化for (int j = 1; j <= n; j++)if (p[j] == '*')dp[0][j] = true;elsebreak;// 3. 填表for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (p[j] == '*')dp[i][j] = dp[i - 1][j] || dp[i][j - 1];elsedp[i][j] =(p[j] == '?' || s[i] == p[j]) && dp[i - 1][j - 1];}}// 4. 返回值return dp[m][n];}
};

10. 正则表达式匹配 - 力扣(LeetCode)

题目解析

状态表示

对于两个字符串之间的dp问题,我们一般的思考方式如下:

  1. 选取第一个字符串的[0,i]区间以及第二个字符串的[0,j]区间当成研究对象,结合题目的要求来定义「状态表示」;

  2. 然后根据两个区间上「最后一个位置的字符」,来进行「分类讨论」,从而确定「状态转移方程」。

我们可以根据上面的策略,解决大部分关于两个字符串之间的dp问题。

因此,我们定义状态表示为: dp[i][j]表示:字符串p[0,j]能否匹配字符串 s 的[0,i]。

状态转移方程

根据最后一个位置的具体状况进行分类讨论,

  1. 如果p[j]为一个小写英文字母,

    1. 如果s[i]==p[j],此时dp[i][j]=dp[i-1][j-1]。

    2. 如果s[i]!=p[j],此时dp[i][j]=false。

  2. 如果p[j]=='.', 此时dp[i][j]=dp[i-1][j-1]。

  3. 如果p[j]=='*',

  4.   观察上述推导,我们发现一个很有趣的东西,dp[i-1][j-2]、dp[i-2][j-2]、dp[i-3][j-2]....横坐标依次减一,纵坐标不变,变化十分有规律。由此我们尝试推导dp[i-1][j]的等式,得到

      dp[i-1][j]=(dp[i-1][j-2]||dp[i-2][j-2]||dp[i-3][j-2]||...||dp[0][j-1]||全部匹配||p[i-1]=='.'),又

      dp[i][j]=(dp[i][j-2]||dp[i-1][j-2]||dp[i-2][j-2]||dp[i-3][j-2]||...||dp[0][j-1]||全部匹配||p[i-1]=='.'),

      得到dp[i][j]=(dp[i][j-2]||dp[i-1][j]);

综上所述,状态转移方程为,

 
    if (p[j] == '*')dp[i][j] = dp[i][j - 2] || (p[j - 1] == '.' || p[j - 1] == s[i]) && dp[i - 1][j];elsedp[i][j] = (p[j] == s[i] || p[j] == '.') && dp[i - 1][j - 1];

初始化

根据状态转移方程,我们知道,想要推导出(i,j)位置的状态需要用到,(i,j-2)(i-1,j)(i-1,j-1)的状态,还需要判断p[j-1]。

注意,如果*出现,前面一定有至少一个元素,所以(i,j-2)位置的状态一定不会越界。所以我们只需要考虑(i-1,j)(i-1,j-1)的状态,以及判断p[j-1]。

当我们在推导蓝色部分的状态时,就会发现越界的情况,也就是没有前驱使得蓝色位置状态可以推导出来,所以我们需要把这些位置进行初始化。

为了简化初始化的过程,我们可以创建虚拟节点,即多添加一行和一列代替原先需要初始化的位置。

添加虚拟节点有几点注意事项,

  1. 对虚拟节点进行初始化,需要保证推导蓝色位置的状态的准确性。

  2. 注意下标映射的关系。

对虚拟节点进行初始化:

dp[i][j]表示:字符串p[0,j]能否匹配字符串 s 的[0,i]。

为了统一状态表示,我们可以定义绿色部分含义为空串。

dp[0][0]表示两个空串是否能匹配,此时dp[0][0]=true。

  1. 当i==0,j>0, 表示s是空串,p[0,j]能否匹配,此时只有可能p[0,j]所有字符表示为“任一字符+*“才能匹配空串。

  2. 当i>0,j==0, 此时s[0,i],p时空串,不可能匹配,dp[i][j]=false。

下标映射的关系:

dp[i][j]表示:字符串p[0,j]能否匹配字符串 s 的[0,i]。

因为多添加了一行和一列,所以实际上dp[i][j]表示s中[0,i-1]区域的所有子序列,与p中[0,j-1]区域子数组相等的子序列个数。

有两种解决办法。

  1. 从dp[i][j]找s,p中对应的位置需要下标减1。即用i-1,j-1找到s,p中的对应位置。

  2. 当然也可以在s,p前面添加一个字符作为平衡,这样dp[i][j]就可以直接与s[i]、t[j]元素进行对应。

实际上下标映射关系改变后,相应的状态表示,状态转移方程都需要改变,但我们只需要注意一下下标的映射关系含义即可。

填表顺序

根据状态转移方程,我们知道,想要推导出(i,j)位置的状态需要用到,(i,j-2)(i-1,j)(i-1,j-1)的状态,还需要判断p[j-1]。

  1. 固定i改变j, i的变化需要从小到大,由于需要用到(i,j-2)位置的状态,所以j的变化需要从小到大。

  2. 固定j改变i, j的变化需要从小到大,由于需要用到(i-1,j)位置的状态,所以i的变化需要从小到大。

返回值

dp[i][j]表示:字符串p[0,j]能否匹配字符串 s 的[0,i]。

根据状态表示和题目意思,我们需要判断字符串p[0,n]能否匹配字符串 s [0,m]。

又因为下标映射关系,所以我们需要返回dp[m][n]。

代码实现

 
class Solution {
public:bool isMatch(string s, string p) {int m = s.size(), n = p.size();s = ' ' + s, p = ' ' + p;// 1. 创建 dp 表vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));// 2. 初始化dp[0][0] = true;for (int j = 2; j <= n; j += 2)if (p[j] == '*')dp[0][j] = true;elsebreak;// 3. 填表for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (p[j] == '*')dp[i][j] =dp[i][j - 2] ||(p[j - 1] == '.' || p[j - 1] == s[i]) && dp[i - 1][j];elsedp[i][j] = (p[j] == s[i] || p[j] == '.') && dp[i - 1][j - 1];}}// 4. 返回值return dp[m][n];}
};

结尾

今天我们学习了动态规划的思想,动态规划思想和数学归纳法思想有一些类似,动态规划在模拟数学归纳法的过程,已知一个最简单的基础解,通过得到前项与后项的推导关系,由这个最简单的基础解,我们可以一步一步推导出我们希望得到的那个解,把我们得到的解依次存放在dp数组中,dp数组中对应的状态,就像是数列里面的每一项。最后感谢您阅读我的文章,对于动态规划系列,我会一直更新,如果您觉得内容有帮助,可以点赞加关注,以快速阅读最新文章。

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

这篇关于【十五】【动态规划】115. 不同的子序列、44. 通配符匹配、10. 正则表达式匹配 ,三道题目深度解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/574174

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

关于最长递增子序列问题概述

《关于最长递增子序列问题概述》本文详细介绍了最长递增子序列问题的定义及两种优化解法:贪心+二分查找和动态规划+状态压缩,贪心+二分查找时间复杂度为O(nlogn),通过维护一个有序的“尾巴”数组来高效... 一、最长递增子序列问题概述1. 问题定义给定一个整数序列,例如 nums = [10, 9, 2

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Java使用POI-TL和JFreeChart动态生成Word报告

《Java使用POI-TL和JFreeChart动态生成Word报告》本文介绍了使用POI-TL和JFreeChart生成包含动态数据和图表的Word报告的方法,并分享了实际开发中的踩坑经验,通过代码... 目录前言一、需求背景二、方案分析三、 POI-TL + JFreeChart 实现3.1 Maven

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查