当前位置: 首页 > news >正文

【算法与数据结构】—— 回文问题

回文问题


目录

  • 1、简介
  • 2、经典的回文问题
    • (1) 判断一个字符串是否为回文
    • (2) 给定字符集求构建的最长回文长度
    • (3) 求最长回文子串
      • 方法一:中心拓展
      • 方法二:Manacher 算法
    • (4) 求回文子串的数目
      • 方法一:中心拓展
      • 方法二:Manacher 算法




1、简介


对于一个给定的字符串,如果它的正序和倒序是相同的,则称这样的字符串为 回文

例如,对于字符串 “aabaa”,它的倒序也为 “aabaa”,因此它是一个回文;对于字符串 “abba”,它的倒序也为 “abba”,因此它也是一个回文;对于字符串 “abbc”,它的倒序为 “cbba”,因此它不是一个回文。

从回文的定义可以看出,回文总是 关于中心对称 的。



2、经典的回文问题


(1) 判断一个字符串是否为回文

根据回文的定义可以知道回文是一个关于中心对称的字符串。那么要判断一个字符串是否为回文,最简单的办法就是定义两个指针 l l l r r r 分别从字符串的首尾向字符串中心进行扫描。在扫描过程中,一旦存在两个字符不相同,则说明这个字符串是不是回文;否则,这个扫描过程会一直进行下去直到两个指针相遇,在这情况下说明当前字符串是一个回文。

根据这样的思路,可以得到判断字符串是否为回文的代码如下:

// 判断字符串 s 是否为回文
bool isPalindrome(const string &s) {int l = 0, r = s.length() - 1;while(l < r && s[l] == s[r]){l++;r--;}if(l < r) return false;else return true;
}

复杂度分析

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

空间复杂度: O ( 1 ) O(1) O(1)


(2) 给定字符集求构建的最长回文长度

给定一个包含大写字母和小写字母的字符串 s s s ,返回由这些字符构造的最长的回文长度。例如,对于给定的字符串 “abccccdd”,能够构建的最长的回文为 “dccaccd”(或 “dccbccd” 等),其长度为 7。

对应题目:LeetCode.409 最长回文串。

分析

这道题乍一看会觉得无从下手,但想通之后会发现很简单。

还是从回文的性质出发,由于回文是关于中心对称的。因此,所有回文总是满足以下两个条件之一:

  1. 若当前回文长度为偶数,则所有字符都将出现偶数次;
  2. 若当前回文长度为奇数,则位于中心位置的字符将出现奇数次,其余所有字符都将出现偶数次;

基于此,从贪心的角度出发,要使构建的回文长度最长,可以把所有出现偶数次的字符都选中。在这基础上,如果还有奇数次的字符(假设出现次数为 t t t),则将这些字符中的 t − 1 t-1 t1 个偶数次字符也选中,并在最后再选择一个出现次数为奇数次的字符位于回文中心。下图展示了该思路的具体执行流程:

给定字符集求构建最长的回文

对应代码:

#include<unordered_map>
using namespace std;// 给定字符集求构建的最长回文长度
int longestPalindrome(const string &s)
{unordered_map<char, int> mp;int slen = s.length();int cnt = 0, tmp, odd = 0;// 统计各字符的出现次数for (int i = 0; i < slen; i++)mp[s[i]]++;// 遍历字典,偶数的字符全部都可用,奇数的只能用 - 1 个数的字符for (const auto &pair : mp){tmp = pair.second;cnt += (tmp - (tmp&1));// 标记是否存在奇数个数的字符if (tmp & 1) odd = 1;}return cnt + odd;
}

复杂度分析

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

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


(3) 求最长回文子串

给定一个字符串 s s s,求 s s s 中的最长回文子串。例如,对于字符串 “cbbd”,其中的最长回文子串为 “bb”;对于字符串 “aasabasa”,其中的最长回文子串为 “asabasa”。

对应题目:LeetCode.5 最长回文子串。

分析

最简单直接的办法就是枚举,即枚举所有子串,然后依次判断当前子串是否为一个回文,如果是就记录当前回文串的长度,并维护更新一个用于记录最大回文长度的变量(和对应位置)。枚举结束时,便可以基于维护的相关变量输出最长的回文子串。对于一个字符串 s s s,枚举其子串的代码如下:

// 双重循环枚举字符串 s 的全部子串
int slen = s.length();
for(int l=0; l<slen; l++)for(int r=l; r<slen; r++){string substr = s.substr(l, r-l+1);/* 判断 substr 是否为回文的代码 */}

这个枚举过程是 O ( n 2 ) O(n^2) O(n2) 的时间复杂度。对于每个子串,还需要判断其是否为回文( O ( n ) O(n) O(n) 的时间复杂度)。因此,该方法最终的时间复杂度为 O ( n 3 ) O(n^3) O(n3),是不能容忍的。因此,这个方法不可取。

方法一:中心拓展

注意到回文是一个对称结构,因此我们可以考虑这样的一种枚举方式:遍历每个字符串并将其作为回文中心,在此基础上再向两侧拓展以寻找当前回文中心的最长边界。在这个过程中,每次都维护更新一个用于记录最大回文长度的变量(和对应位置),遍历结束后便能得到最长的回文子串。在这个枚举方案中,遍历原始字符串的时间复杂度为 O ( n ) O(n) O(n),向两侧拓展寻找回文中心的最长边界的时间复杂度也为 O ( n ) O(n) O(n)。因此,中心拓展方法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

具体实现时,需要处理一个问题:如何有序地枚举所有回文中心?之所以有这个问题,是因为回文需要分长度为奇数长度为偶数的两种情况。如果回文长度是奇数,那么回文中心是一个字符;如果回文长度是偶数,那么中心是两个字符。对于某个字符串 “abcbd”,它的回文中心有以下两种划分方式:

  1. 回文中心为单字符,即 “a”、“b”、“c”、“b”、“d”,亦即原字符串本身的长度, n n n
  2. 回文中心为双字符,即 “ab”、“bc”、“cb”、“bd”,亦即原字符串本身的长度再减 1, n − 1 n-1 n1

因此,对长度为 n n n 的字符串 s s s,我们需要枚举的回文中心有 n + ( n − 1 ) = 2 n − 1 n+(n-1)=2n-1 n+(n1)=2n1 种情况。

中心拓展的思路如下:

中心拓展求解最长回文子串

对应代码:

// 给定字符串,求最长的回文子串
string longestPalindrome(const string &s)
{// longestPd 记录最长的回文子串长度,posPd 记录对应最长子串的起始位置int slen = s.size(), longestPd = 0, posPd;int l, r, lenPd;for (int i = 0, maxIter = 2 * slen - 1; i < maxIter; i++){// 确定当前的回文中心l = i / 2, r = i / 2 + (i & 1);// 寻找当前回文中心的最长边界while (l >= 0 && r < slen && s[l] == s[r])l--, r++;// 计算当前回文的长度lenPd = r - l - 1;// 更新if (lenPd > longestPd){longestPd = lenPd;posPd = l + 1;}}return s.substr(posPd, longestPd);
}

复杂度分析

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

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


方法二:Manacher 算法

Manacher 算法是一种在线性时间内求解最长回文子串的算法。

Manacher 算法也会面临「方法一」中的奇数长度和偶数长度的问题,它的处理方式是在所有的相邻字符中间(以及字符串的首尾位置)插入 #,比如 “abaa” 会被处理成 “#a#b#a#a#”,这样可以保证所有找到的回文串都是奇数长度的,亦即所有的回文都是以单字符为中心的。在下面的谈论中,我们用 s s s 表示添加了 “#” 的新字符串。

在学习 Manacher 算法之前,需要先了解以下定义。

回文半径 RL:回文中最左(或最右)位置的字符与其对称轴之间的字符数量称为回文半径(包含对称轴本身),我们用 R L RL RL 表示。因此,对字符串 s s s 中的某个字符 s [ i ] s[i] s[i] 而言,我们可以用数组 R L [ i ] RL[i] RL[i] 表示以字符 s [ i ] s[i] s[i] 为回文中心得到的回文半径。例如,对于字符串 “aba” 和字符串 “abba” 有以下定义:

位置:	0	1	2	3	4	5	6
字符:	#	a	#	b	#	a	#
RL:	1	2	1	4	1	2	1
RL-1:	0	1	0	3	0	1	0位置:	0	1	2	3	4	5	6	7	8
字符:	#	a	#	b	#	b	#	a	#
RL:	1	2	1	2	5	2	1	2	1
RL-1:	0	1	0	1	4	1	0	1	0

对字符串 “#a#b#a#” 而言,第一个字符 “a” 的回文半径为 2(子串 “#a#” 构成一个回文,最左侧的 “#” 到 “a” 共计 2 个字符 “#a”),因此 R L [ 1 ] = 2 RL[1]=2 RL[1]=2

对字符串 “#a#b#b#a#” 而言,正中间的字符 “#” 的回文半径为 5(整个字符串 “#a#b#b#a#” 构成一个回文,最左侧的 “#” 到正中间 “a” 共计 5 个字符 “#a#b#”),因此 R L [ 4 ] = 5 RL[4]=5 RL[4]=5

注意到在上面给出的例子中我们还给出了 R L − 1 RL-1 RL1(实际上就是 “最左(或最右)位置的字符与其对称轴之间的字符数(不包含对称轴本身)”),可以发现,各 R L [ i ] − 1 RL[i]-1 RL[i]1 的值恰好等于在原字符串中以字符 s [ i ] s[i] s[i] 为对称轴的回文长度。例如,在字符串 “#a#b#a#” 中, R L [ 3 ] − 1 = 3 RL[3]-1=3 RL[3]1=3,恰好等于原始字符串 “aba” 以字符 b b b 为回文中心得到的最长回文长度。在字符串 “#a#b#b#a#” 中, R L [ 4 ] − 1 = 4 RL[4]-1=4 RL[4]1=4,恰好等于原始字符串 “abba” 以字符 b b bb bb 为回文中心得到的最长回文长度(“#” 是插入的字符,可以理解为一个虚拟字符。如果处理 R L [ i ] RL[i] RL[i] 时对应的 s [ i ] s[i] s[i] 是虚拟字符,它对应在原字符串中的回文中心就是以 s [ i ] s[i] s[i] 前后两个字符组成的双字符)。

另一方面,由于回文是对称的,因此对于任意的 R L [ i ] RL[i] RL[i] 都有这样的一个性质: i − R L [ i ] + 1 2 = \frac{i-RL[i]+1}{2}= 2iRL[i]+1=以字符 s [ i ] s[i] s[i] 为回文中心构成的最长回文在原字符串中的起始位置。例如,在字符串 “#a#b#a#” 中, 3 − R L [ 3 ] + 1 2 = 0 \frac{3-RL[3]+1}{2}=0 23RL[3]+1=0,而在原始字符串 “aba” 中,以字符 “b” 为回文中心的起始索引也是 0;在字符串 “#a#b#b#a#” 中, 4 − R L [ 4 ] + 1 2 = 0 \frac{4-RL[4]+1}{2}=0 24RL[4]+1=0,而在原始字符串 “abba” 中,以字符 “bb” 为回文中心的起始索引也是 0。

在熟悉以上概念后,“求最长回文子串” 的任务就被转换为 “求 R L RL RL 数组”。更准确地说,是在线性时间复杂度内求 R L RL RL 数组。Manacher 算法计算 R L RL RL 数组的整体流程和中心拓展方法是一致的:(1) 遍历字符串 s s s(假设遍历指针为 i i i);(2) 计算以各字符 s [ i ] s[i] s[i] 为回文中心的回文半径(即 R L [ i ] RL[i] RL[i])。与中心拓展方法不同的是,Manacher 算法充分利用了回文 中心对称 的特性,进而大幅降低了许多重复计算。

最远已访问指针 VisitedPos:在计算以字符 s [ i ] s[i] s[i] 为回文中心的回文半径时,我们定义 “沿着与遍历方向一致的被访问的最远位置为 V i s i t e d P o s VisitedPos VisitedPos”。这就是说, V i s i t e d P o s VisitedPos VisitedPos 指示了在更新 R L RL RL 数组时,被访问到的最远位置。

最远已访问指针对应的回文中心指针 MidPos:为便于相关参数的计算,还需要定义最远已访问指针 V i s i t e d P o s VisitedPos VisitedPos 对应的回文中心位置 M i d P o s MidPos MidPos,显然, M i d P o s = ⌊ V i s i t e d P o s 2 ⌋ MidPos=\left\lfloor\frac{VisitedPos}{2}\right\rfloor MidPos=2VisitedPos

下图给出了 V i s i t e d P o s VisitedPos VisitedPos M i d P o s MidPos MidPos 的具体更新步骤:

VisitedPos 和 MidPos 指针的更新过程

关于 V i s i t e d P o s VisitedPos VisitedPos M i d P o s MidPos MidPos,需要注意以下两个关键点:

  1. V i s i t e d P o s ≥ i VisitedPos \geq i VisitedPosi。最远已访问指针 V i s i t e d P o s VisitedPos VisitedPos 始终不小于遍历指针 i i i
  2. i ≥ M i d P o s i \geq MidPos iMidPos。理想情况下,每次中心拓展 V i s i t e d P o s VisitedPos VisitedPos 都进行了更新,则 i = ⌊ V i s i t e d 2 ⌋ = M i d P o s i=\left\lfloor\frac{Visited}{2}\right\rfloor=MidPos i=2Visited=MidPos;其他情况下, V i s i t e d P o s VisitedPos VisitedPos 未被更新,则 i = i + 1 i = i+1 i=i+1 M i d P o s MidPos MidPos 不发生变化,因此有 i > M i d P o s i>MidPos i>MidPos。综上, i ≥ M i d P o s i \geq MidPos iMidPos

下面正式进入核心环节:Manacher 算法如何将 R L RL RL 数组的求解降低至线性时间复杂度的。

首先要明确一点:由 V i s i t e d P o s VisitedPos VisitedPos M i d P o s MidPos MidPos 共同指示的回文是一个关于 M i d P o s MidPos MidPos 对称的字符串。例如,在下图中,字符串 s [ l V i s i t e d P o s , M i d P o s ] s[lVisitedPos, MidPos] s[lVisitedPos,MidPos] s [ M i d P o s , r V i s i t e d P o s ] s[MidPos, rVisitedPos] s[MidPos,rVisitedPos] 完全一致。

VisitedPos 和 MidPos 指示的回文对称

在这情况下,当我们计算以 s [ 13 ] s[13] s[13] 为回文中心的回文半径 R L [ 13 ] RL[13] RL[13] 时,就不需要再执行一次中心拓展过程,因为 s [ 13 ] s[13] s[13] s [ 7 ] s[7] s[7] 关于 M i d P o s = 10 MidPos=10 MidPos=10 对称,而 R L [ 7 ] RL[7] RL[7] 在之前已经计算过了,所以可以确定 R L [ 13 ] RL[13] RL[13] 的下限,即 R L [ 13 ] ≥ R L [ 7 ] = 3 RL[13]\geq RL[7]=3 RL[13]RL[7]=3,然后在这基础上再对 R L [ 13 ] RL[13] RL[13] 进行中心拓展。这样一来,就大幅降低了计算 R L [ 13 ] RL[13] RL[13] 的开销。注:根据对称法则 i i i j j j M i d P o s MidPos MidPos 存在关系 i + j = 2 ∗ M i d P o s i+j=2*MidPos i+j=2MidPos,即 j = 2 ∗ M i d P o s − i j=2*MidPos-i j=2MidPosi

但是,直接取 R L [ 13 ] ≥ R L [ 7 ] RL[13]\geq RL[7] RL[13]RL[7] 是不准确的。考虑一种情况:以 s [ 7 ] s[7] s[7] 为回文中心的半径长度超过了 l V i s i t e d P o s lVisitedPos lVisitedPos 指示的位置,如下图所示。

前面的回文过长的情况

在这种情况下,由于以 M i d P o s MidPos MidPos 为回文中心的回文仅能保证子串 s [ l V i s i t e d P o s , M i d P o s ] s[lVisitedPos, MidPos] s[lVisitedPos,MidPos] s [ M i d P o s , r V i s i t e d P o s ] s[MidPos, rVisitedPos] s[MidPos,rVisitedPos] 一致,而无法保证在超出这个范围以外的部分也相同。此时, R L [ i ] RL[i] RL[i] 的下限又该如何确定呢?这时候,就需要从回文 中心对称 的特性出发进行思考了,观察下图:

L 指示回文过长时 i 的初始化规则

证明:字符串 S 3 S_3 S3 S 4 S_4 S4 关于 i i i 对称

因为 回文 P a l i n d r o m e 1 Palindrome1 Palindrome1 关于 j j j 对称
所以 字符串 S 1 S_1 S1 S 2 S_2 S2 关于 j j j 对称
因为 回文 P a l i n d r o m e 2 Palindrome2 Palindrome2 关于 M i d P o s MidPos MidPos 对称
所以 字符串 S 2 S_2 S2 S 3 S_3 S3 关于 M i d P o s MidPos MidPos 对称
由于 S 1 S_1 S1 S 2 S_2 S2 关于 j j j 对称, S 2 S_2 S2 S 3 S_3 S3 关于 M i d P o s MidPos MidPos 对称
所以 字符串 S 1 = S 3 S_1 = S_3 S1=S3
又因为 回文 P a l i n d r o m e 2 Palindrome2 Palindrome2 关于 M i d P o s MidPos MidPos 对称
所以 字符串 S 1 S_1 S1 S 4 S_4 S4 关于 M i d P o s MidPos MidPos 对称
综合 S 1 = S 3 S_1 = S_3 S1=S3 可知, S 3 S_3 S3 S 4 S_4 S4 关于 M i d P o s MidPos MidPos 对称

因此,当以 s [ j ] s[j] s[j] 为中心的回文范围超出了 l V i s i t e d P o s lVisitedPos lVisitedPos 时,我们可以确定字符 s [ i ] s[i] s[i] 的回文半径不低于 V i s i t e d P o s − i + 1 VisitedPos-i+1 VisitedPosi+1,即 R L [ i ] ≥ ( r V i s i t e d P o s − i + 1 ) RL[i] \geq (rVisitedPos-i+1) RL[i](rVisitedPosi+1)


综上,可以将字符 s [ i ] s[i] s[i] 的回文半径 R L [ i ] RL[i] RL[i] 的初始化归结为以下 3 种情况:

  1. i = V i s i t e d P o s i = VisitedPos i=VisitedPos :说明以 i i i 为中心的回文还没有任何一个部分被访问过,这种情况下只能从 i i i 的左右两边进行中心扩展,即初始化 R L [ i ] = 1 RL[i]=1 RL[i]=1
  2. i < V i s i t e d P o s i < VisitedPos i<VisitedPos
    • R L [ j ] ≤ ( V i s i t e d P o s − i ) RL[j] \leq (VisitedPos-i) RL[j](VisitedPosi) 时(以 i i i 的对称位置 j j j 为中心的回文未超过 V i s i t e d P o s VisitedPos VisitedPos 对应回文的范围),初始化 R L [ i ] = R [ j ] RL[i]=R[j] RL[i]=R[j]
    • R L [ j ] > ( V i s i t e d P o s − i ) RL[j] >(VisitedPos-i) RL[j]>(VisitedPosi) 时(以 i i i 的对称位置 j j j 为中心的回文超过了 V i s i t e d P o s VisitedPos VisitedPos 对应回文的范围),初始化 R L [ i ] = V i s i t e d P o s − i RL[i]=VisitedPos-i RL[i]=VisitedPosi

以上便是 Manacher 算法的核心思路,在寻找最长的回文子串时,还需要额外引入一个变量来记录当前找到的最长回文长度。可能你会疑惑:确定了最长回文长度,还需要一个变量保存这个回文对应的起点(或者中心位置)才能得到对应的回文串?实际上并不需要。还记得么,前面我们曾说过,在对原始字符串插入 “#” 后,各 R L RL RL 通过 i − R L [ i ] + 1 2 \frac{i-RL[i]+1}{2} 2iRL[i]+1 便可以得到该回文对应的起点索引。

于是,可以写出 Manacher 算法的完整代码如下:

string longestPalindromeStr(const string &s)
{// 预处理(填充 “#”)string s_;for (int i = 0, slen = s.length(); i < slen; i++){s_ += "#";s_ += s[i];}s_ += "#";// 寻找最长回文子串int slen = s_.size(), visitedPos = 0, midPos = 0, maxRLPos = 0;vector<int> RL(slen, 0);for (int i = 0; i < slen; i++){// 当前遍历字符的位置位于 visitedPos 之前if (i < visitedPos)RL[i] = min(RL[2 * midPos - i], visitedPos - i + 1);// 当前遍历字符的位置与 visitedPos 重合elseRL[i] = 1;// 中心拓展(注意处理边界)while (i - RL[i] >= 0 && i + RL[i] < slen && s_[i - RL[i]] == s_[i + RL[i]])RL[i] += 1;// 更新 visitedPos 和 midPosif (i + RL[i] - 1 > visitedPos){visitedPos = RL[i];midPos = i;}// 更新 maxRLif (RL[i] > RL[maxRLPos])maxRLPos = i;}return s.substr((maxRLPos - RL[maxRLPos] + 1) / 2, RL[maxRLPos] - 1);
}

复杂度分析

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

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


(4) 求回文子串的数目

给定一个字符串 s s s,求 s s s 中全部的回文子串数目。例如,对于字符串 “abc”,其中的回文子串有 “a”、“b”、“c” 共 3 个,因此返回 3;对于字符串 “aba”,其中的回文子串有 “a”、“b”、“c” 、“aba” 共 4 个,因此返回 4。

对应题目:LeetCode.647 回文子串。

分析

方法一:中心拓展

可以利用中心拓展方式枚举全部的回文子串,并进行统计,对应代码如下:

int countSubstrings(string s)
{int slen = s.size(), count = 0;int l, r;for (int i = 0, maxIter = 2 * slen - 1; i < maxIter; i++){// 确定当前的回文中心l = i / 2, r = i / 2 + (i & 1);// 枚举以当前字符为中心的全部回文while (l >= 0 && r < n && s[l] == s[r]){--l;++r;++count;}}return count;
}

复杂度分析

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

空间复杂度: O ( 1 ) O(1) O(1)


方法二:Manacher 算法

从前面的分析知道, Manacher 算法可以在线性时间复杂度度内求出全部的 R L [ i ] RL[i] RL[i](即所有字符的最长回文半径),这实际上就已经枚举了所有的回文。那么,要统计给定字符的全部回文子串,就只需要在 Manacher 算法内部进行累加即可。

需要注意的是,由于对字符串添加了 “#”,就需要对这些字符进行排除。如何排除呢?

很简单,注意到在添加 “#” 后,所有的回文都是以单字符为中心的,在这种情况下,所有原本是双字符为中心的回文(对应在新串中为 “#”)的最小 R L [ i ] RL[i] RL[i] 为 3;所有单字符为中心的回文的最小 R L [ i ] RL[i] RL[i] 也为 3;而对于无效的 “#” 符的 R L [ i ] RL[i] RL[i] 值就只能取到 1。因此,对所有的 R L [ i ] RL[i] RL[i],其真实贡献可以取 R L [ i ] − 1 2 \frac{RL[i]-1}{2} 2RL[i]1,这样一来,就消除了 “#” 对真实结果的影响。

下面给出利用 Manacher 算法求解该问题的完整代码:

int countSubstrings(const string &s)
{// 预处理(填充 “#”)string s_;for (int i = 0, slen = s.length(); i < slen; i++){s_ += "#";s_ += s[i];}s_ += "#";// 统计回文子串个数int slen = s_.size(), visitedPos = 0, midPos = 0, count = 0;vector<int> RL(slen, 0);for (int i = 0; i < slen; i++){// 当前遍历字符的位置位于 visitedPos 之前if (i < visitedPos)RL[i] = min(RL[2 * midPos - i], visitedPos - i + 1);// 当前遍历字符的位置与 visitedPos 重合elseRL[i] = 1;// 中心拓展(注意处理边界)while (i - RL[i] >= 0 && i + RL[i] < slen && s_[i - RL[i]] == s_[i + RL[i]])RL[i] += 1;// 更新 visitedPos 和 midPosif (i + RL[i] - 1 > visitedPos){visitedPos = RL[i];midPos = i;}// 统计回文子串数量count += RL[i] / 2;}return count;
}

复杂度分析

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

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


END


相关文章:

【算法与数据结构】—— 回文问题

回文问题 目录 1、简介2、经典的回文问题(1) 判断一个字符串是否为回文(2) 给定字符集求构建的最长回文长度(3) 求最长回文子串方法一&#xff1a;中心拓展方法二&#xff1a;Manacher 算法 (4) 求回文子串的数目方法一&#xff1a;中心拓展方法二&#xff1a;Manacher 算法 1、…...

用vscode写latex-1

一般大伙使用 LaTeX 大体有两种方案&#xff0c; 一种是在本地配置环境或使用本地的软件&#xff0c;如 vscode LaTeX&#xff0c;texlive&#xff0c;lyx 等等&#xff1b; 另一种是线上 LaTeX 平台&#xff0c;其中用的最多的是 Overleaf&#xff0c;还有一部分高校也有自…...

爬虫基础之爬取歌曲宝歌曲批量下载

声明&#xff1a;本案列仅供学习交流使用 任何用于非法用途均与本作者无关 需求分析: 网站:邓紫棋-mp3在线免费下载-歌曲宝-找歌就用歌曲宝-MP3音乐高品质在线免费下载 (gequbao.com) 爬取 歌曲名 歌曲 实现歌手名称下载所有歌曲 本案列所使用的模块 requests (发送…...

GitLab CI/CD使用runner实现自动化部署前端Vue2 后端.Net 7 Zr.Admin项目

1、查看gitlab版本 建议安装的runner版本和gitlab保持一致 2、查找runner 执行 yum list gitlab-runner --showduplicates | sort -r 找到符合gitlab版本的runner&#xff0c;我这里选择 14.9.1版本 如果执行出现找不到下载源&#xff0c;添加官方仓库 执行 curl -L &quo…...

web前端第五次作业---制作菜单

制作菜单 代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style…...

软件系统安全逆向分析-混淆对抗

1. 概述 在一般的软件中&#xff0c;我们逆向分析时候通常都不能直接看到软件的明文源代码&#xff0c;或多或少存在着混淆对抗的操作。下面&#xff0c;我会实践操作一个例子从无从下手到攻破目标。 花指令对抗虚函数表RC4 2. 实战-donntyousee 题目载体为具有漏洞的小型软…...

HAMi + prometheus-k8s + grafana实现vgpu虚拟化监控

最近长沙跑了半个多月&#xff0c;跟甲方客户对了下项目指标&#xff0c;许久没更新 回来后继续研究如何实现 grafana实现HAMi vgpu虚拟化监控&#xff0c;毕竟合同里写了需要体现gpu资源限制和算力共享以及体现算力卡资源共享监控 先说下为啥要用HAMi吧&#xff0c; 一个重要原…...

Java基于SSM框架的在线视频教育系统小程序【附源码、文档】

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…...

mysql本地安装和pycharm链接数据库操作

MySQL本地安装和相关操作 Python相关&#xff1a;基础、函数、数据类型、面向、模块。 前端开发&#xff1a;HTML、CSS、JavaScript、jQuery。【静态页面】 Java前端&#xff1b; Python前端&#xff1b; Go前端 -> 【动态页面】直观&#xff1a; 静态&#xff0c;写死了…...

Unity编程与游戏开发-编程与游戏开发的关系

游戏开发是一个复杂的多领域合作过程,涵盖了从创意构思到最终实现的多个方面。在这个过程中,技术、设计与美术三大核心要素相互交织,缺一不可。在游戏开发的过程中,Unity作为一款强大的跨平台游戏引擎,凭借其高效的开发工具和庞大的社区支持,成为了很多游戏开发者的首选工…...

2025年第三届“华数杯”国际赛A题解题思路与代码(Python版)

游泳竞技策略优化模型代码详解 第一题&#xff1a;速度优化模型 在这一部分&#xff0c;我们将详细解析如何通过数学建模来优化游泳运动员在不同距离比赛中的速度分配策略。 1. 模型概述 我们的模型主要包含三个核心文件&#xff1a; speed_optimization.py: 速度优化的核…...

针对服务器磁盘爆满,MySql数据库始终无法启动,怎么解决

&#xff08;点击即可进入聊天助手&#xff09; 很多站长在运营网站的过程当中都会遇到一个问题,就是网站突然无法打开,数据一直无法启动 无论是强制重启还是,删除网站内的所有应用,数据库一直无法启动 这个时候,就需要常见的运维手段了,需要对服务器后台各个资源,进行逐一排查…...

[Android]service命令的使用

在前面的讨论中,我们说到,如果在客户端懒得使用aidl文件生成的接口类进行binder,可以使用IBinder的transcat方法 Parcel dataParcel = Parcel.obtain(); Parcel resultParcel = Parcel.obtain();dataParcel.writeInterfaceToken(DESCRIPTOR);//发起请求 aProxyBinder.trans…...

【芯片封测学习专栏 -- Substrate | RDL Interposer | Si Interposer | 嵌入式硅桥(EMIB)详细介绍】

请阅读【嵌入式开发学习必备专栏 Cache | MMU | AMBA BUS | CoreSight | Trace32 | CoreLink | ARM GCC | CSH】 文章目录 OverviewSubstrate&#xff08;衬底或基板&#xff09;Substrate 定义Substrate 特点与作用Substrate 实例 RDL Interposer&#xff08;重布线层中介层&a…...

spring cloud注册nacos并从nacos上拉取配置文件,spring cloud不会自动读取bootstrap.yml文件

目录 踩坑问题记录前言版本说明spring cloudb不会自动读取bootstrap.yml文件问题解决spring cloud注册nacos并从nacos上拉取配置文件后话 踩坑问题记录 1、spring cloudb不会自动读取bootstrap.yml文件 2、spring cloud注册nacos并从nacos上拉取配置文件 前言 使用cloud Ali…...

【深度学习地学应用|滑坡制图、变化检测、多目标域适应、感知学习、深度学习】跨域大尺度遥感影像滑坡制图方法:基于原型引导的领域感知渐进表示学习(一)

【深度学习地学应用|滑坡制图、变化检测、多目标域适应、感知学习、深度学习】跨域大尺度遥感影像滑坡制图方法&#xff1a;基于原型引导的领域感知渐进表示学习&#xff08;一&#xff09; 【深度学习地学应用|滑坡制图、变化检测、多目标域适应、感知学习、深度学习】跨域大…...

Spring Boot 支持哪些日志框架

Spring Boot 支持多种日志框架&#xff0c;主要包括以下几种&#xff1a; SLF4J (Simple Logging Facade for Java) Logback&#xff08;默认&#xff09;Log4j 2Java Util Logging (JUL) 其中&#xff0c;Spring Boot 默认使用 SLF4J 和 Logback 作为日志框架。如果你需要使…...

【翻译】2025年华数杯国际赛数学建模题目+翻译pdf自取

保存至本地网盘 链接&#xff1a;https://pan.quark.cn/s/f82a1fa7ed87 提取码&#xff1a;6UUw 2025年“华数杯”国际大学生数学建模竞赛比赛时间于2025年1月11日&#xff08;周六&#xff09;06:00开始&#xff0c;至1月15日&#xff08;周三&#xff09;09:00结束&#xff…...

qt 窗口(window/widget)绘制/渲染顺序 QPainter QPaintDevice Qpainter渲染 失效 无效 原因

qt窗体布局 窗体渲染过程 qt中窗体渲染逻辑顺序为 本窗体->子窗体/控件 递归&#xff0c;也就是说先渲染父窗体再渲染子窗体。其中子窗体按加入时的先后顺序进行渲染。通过下方的函数调用堆栈可以看出窗体都是在widget组件源码的widgetprivate::drawwidget中进行渲染的&am…...

TIOBE编程语言排行靠前的编程语言的吉祥物

Python的吉祥物&#xff1a;小蟒蛇 Python语言的吉祥物是一只名叫"Pythonidae"&#xff08;或简称"Py"&#xff09;的小蟒蛇。这个吉祥物由Tobias Kohn设计于2005年&#xff0c;它的形象借鉴了真实的蟒蛇&#xff0c;但加入了一些可爱和友善的特点。小蟒蛇…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

k8s从入门到放弃之Pod的容器探针检测

k8s从入门到放弃之Pod的容器探针检测 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;容器探测是指kubelet对容器执行定期诊断的过程&#xff0c;以确保容器中的应用程序处于预期的状态。这些探测是保障应用健康和高可用性的重要机制。Kubernetes提供了两种种类型…...