
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在算法笔试与面试中,字符串是当之无愧的高频核心考点,贯穿入门到进阶全阶段,无论是互联网大厂笔试还是算法竞赛,字符串相关题型的出现频率始终稳居前列.其题型看似灵活多变,实则暗藏固定套路,核心围绕双指针、滑动窗口、哈希计数、回文处理、KMP、栈等经典方法展开,多数新手往往因缺乏系统梳理,陷入"刷一道会一道,换一道就卡壳"的困境.本文聚焦字符串实战突破,摒弃冗余理论,以"题型分类 + 通用模板 + 经典例题"为核心,梳理各类高频题型的解题逻辑,提炼可直接套用的代码模板,帮助学习者快速掌握解题关键,避开常见误区,高效提升字符串题型的解题能力,从容应对笔试面试中的各类字符串难题.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!
目录
1.字符串思想背景介绍
字符串作为计算机科学中最基础、最常用的数据表示形式,本质上是由有限个字符组成的线性有序序列,是文本信息、程序代码、网络报文、用户输入、数据序列化等场景下的核心载体.从简单的文本编辑、关键词检索,到编译原理中的词法分析、信息安全中的哈希校验,再到人工智能领域的自然语言处理,字符串处理贯穿了计算机应用与算法设计的几乎所有分支,是连接底层数据结构与上层业务逻辑的关键桥梁.正因字符串问题入门门槛低、题型覆盖面广、解法梯度清晰,它成为各类笔试、面试与算法竞赛中的必考内容.初学者常因字符串操作琐碎、边界条件繁多而感到棘手,而熟练掌握其背后的统一思想与解题范式,就能将纷繁复杂的字符串题目归纳为有限的几类通用模型,实现从"逐个解题"到"按模板秒杀"的跨越.本文正是基于这一背景,提炼字符串问题的核心思想与通用解法,为实战刷题提供系统化的思路支撑.
2.最长公共前缀(OJ题)

算法思路:解法⼀(两两⽐较):
我们可以先找出前两个的最⻓公共前缀,然后拿这个最⻓公共前缀依次与后⾯的字符串⽐较,这样就
可以找出所有字符串的最⻓公共前缀.
核心代码
cpp
class Solution
{
public:
string longestCommonPrefix(vector<string>& strs)
{
//解法⼀:两两⽐较
string ret = strs[0];
for (int i = 1; i < strs.size(); i++)
ret = findCommon(ret, strs[i]);
return ret;
}
string findCommon(string& s1, string& s2)
{
int i = 0;
while (i < min(s1.size(), s2.size()) && s1[i] == s2[i])
i++;
return s1.substr(0, i);
}
};
算法思路:解法⼆(统⼀⽐较):
题⽬要求多个字符串的公共前缀,我们可以逐位⽐较这些字符串,哪⼀位出现了不同,就在哪⼀位截⽌.
核心代码
cpp
class Solution
{
public:
string longestCommonPrefix(vector<string>& strs)
{
//解法⼆:统⼀⽐较
for (int i = 0; i < strs[0].size(); i++)
{
char tmp = strs[0][i];
for (int j = 1; j < strs.size(); j++)
if (i == strs[j].size() || tmp != strs[j][i])
return strs[0].substr(0, i);
}
return strs[0];
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution
{
public:
string longestCommonPrefix(vector<string>& strs)
{
//边界处理:数组为空,直接返回空字符串
if (strs.empty()) return "";
//遍历第一个字符串的每一个字符,作为基准
for (int i = 0; i < strs[0].size(); i++)
{
char tmp = strs[0][i];
//遍历数组中剩余所有字符串,对比第i个字符
for (int j = 1; j < strs.size(); j++)
//两种终止情况:
//1.当前字符串长度不足i → 无法匹配
//2.字符不相等 → 公共前缀终止
if (i == strs[j].size() || tmp != strs[j][i])
return strs[0].substr(0, i);
}
//第一个字符串就是完整的公共前缀
return strs[0];
}
};
void testLongestCommonPrefix(vector<string> strs) {
Solution sol;
string res = sol.longestCommonPrefix(strs);
cout << "输入数组:";
for (auto& s : strs) cout << s << " ";
cout << "\n最长公共前缀:\"" << res << "\"\n-------------------------\n";
}
int main() {
testLongestCommonPrefix({"flower","flow","flight"});
testLongestCommonPrefix({"dog","racecar","car"});
testLongestCommonPrefix({"a"});
testLongestCommonPrefix({"ab", "a"});
testLongestCommonPrefix({});
return 0;
}

3.最长回文子串(OJ题)

算法思路:解法(中⼼扩散):
枚举每⼀个可能的⼦串⾮常费时,有没有⽐较简单⼀点的⽅法呢?
对于⼀个⼦串⽽⾔,如果它是回⽂串,并且⻓度⼤于2,那么将它⾸尾的两个字⺟去除之后,它仍然是个回⽂串.如此这样去除,⼀直除到⻓度⼩于等于2时呢?⻓度为1的,⾃⾝与⾃⾝就构成回⽂;⽽⻓度为2的,就要判断这两个字符是否相等了.从这个性质可以反推出来,从回⽂串的中⼼开始,往左读和往右读也是⼀样的.那么,是否可以枚举回⽂串的中⼼呢?从中⼼向两边扩展,如果两边的字⺟相同,我们就可以继续扩展;如果不同,我们就停⽌扩展.这样只需要⼀层 for 循环,我们就可以完成先前两层 for 循环的⼯作量.
核心代码
cpp
class Solution
{
public:
string longestPalindrome(string s)
{
//中⼼扩展算法
int begin = 0, len = 0, n = s.size();
for (int i = 0; i < n; i++) //依次枚举所有的中点
{
//先做⼀次奇数⻓度的扩展
int left = i, right = i;
while (left >= 0 && right < n && s[left] == s[right])
{
left--;
right++;
}
if (right - left - 1 > len)
{
begin = left + 1;
len = right - left - 1;
}
//偶数⻓度的扩展
left = i, right = i + 1;
while (left >= 0 && right < n && s[left] == s[right])
{
left--;
right++;
}
if (right - left - 1 > len)
{
begin = left + 1;
len = right - left - 1;
}
}
return s.substr(begin, len);
}
};
完整测试代码
cpp
#include <iostream>
#include <string>
using namespace std;
//核心解法:中心扩展算法 最长回文子串
class Solution
{
public:
string longestPalindrome(string s)
{
//边界处理:空字符串直接返回
if(s.empty()) return "";
int begin = 0, len = 0, n = s.size();
for (int i = 0; i < n; i++) //依次枚举所有的中点
{
//先做一次奇数长度的扩展(中心为单个字符)
int left = i, right = i;
while (left >= 0 && right < n && s[left] == s[right])
{
left--;
right++;
}
//更新最长回文子串的起始位置和长度
if (right - left - 1 > len)
{
begin = left + 1;
len = right - left - 1;
}
//偶数长度的扩展(中心为两个相邻字符)
left = i, right = i + 1;
while (left >= 0 && right < n && s[left] == s[right])
{
left--;
right++;
}
if (right - left - 1 > len)
{
begin = left + 1;
len = right - left - 1;
}
}
//截取最长回文子串
return s.substr(begin, len);
}
};
void testLongestPalindrome(string s) {
Solution sol;
string res = sol.longestPalindrome(s);
cout << "输入字符串:\"" << s << "\"" << endl;
cout << "最长回文子串:\"" << res << "\"" << endl;
cout << "-------------------------" << endl;
}
int main() {
testLongestPalindrome("babad");
testLongestPalindrome("cbbd");
testLongestPalindrome("a");
testLongestPalindrome("");
testLongestPalindrome("ac");
testLongestPalindrome("aaaaa");
return 0;
}

4.二进制求和(OJ题)

算法思路:解法(模拟⼗进制的⼤数相加的过程):
模拟⼗进制中我们列竖式计算两个数之和的过程.但是这⾥是⼆进制的求和,我们不是逢⼗进⼀,⽽是逢⼆进⼀.
核心代码
cpp
class Solution
{
public:
string addBinary(string a, string b)
{
string ret;
int cur1 = a.size() - 1, cur2 = b.size() - 1, t = 0;
while (cur1 >= 0 || cur2 >= 0 || t)
{
if (cur1 >= 0)
t += a[cur1--] - '0';
if (cur2 >= 0)
t += b[cur2--] - '0';
ret += t % 2 + '0';
t /= 2;
}
reverse(ret.begin(), ret.end());
return ret;
}
};
完整测试代码
cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Solution
{
public:
string addBinary(string a, string b)
{
string ret;
//cur1、cur2 分别指向两个字符串末尾,t 为进位
int cur1 = a.size() - 1, cur2 = b.size() - 1, t = 0;
//循环条件:未遍历完字符串 或 仍有进位
while (cur1 >= 0 || cur2 >= 0 || t)
{
//累加当前位数字
if (cur1 >= 0)
t += a[cur1--] - '0';
if (cur2 >= 0)
t += b[cur2--] - '0';
//计算当前位结果
ret += t % 2 + '0';
//更新进位
t /= 2;
}
//结果反转,得到正确顺序
reverse(ret.begin(), ret.end());
return ret;
}
};
void testAddBinary(string a, string b) {
Solution sol;
string res = sol.addBinary(a, b);
cout << "输入:a = \"" << a << "\", b = \"" << b << "\"" << endl;
cout << "二进制求和结果:\"" << res << "\"" << endl;
cout << "-------------------------" << endl;
}
int main() {
testAddBinary("11", "1");
testAddBinary("1010", "1011");
testAddBinary("0", "0");
testAddBinary("1", "0");
testAddBinary("1111", "1111");
testAddBinary("1", "11111111");
return 0;
}

5.字符串相乘(OJ题)

算法思路:解法(⽆进位相乘然后相加,最后处理进位):
整体思路就是模拟我们⼩学列竖式计算两个数相乘的过程.但是为了我们书写代码的⽅便性,我们选择⼀种优化版本的,就是在计算两数相乘的时候,先不考虑进位,等到所有结果计算完毕之后,再去考虑进位.如下图:

我们先看整体逻辑:
- 反转字符串(让低位对齐,方便从个位开始计算)
- 无进位相乘累加(用数组存每一位的乘积和)
- 统一处理所有进位(得到每一位的最终数字)
- 去除前导零、反转回正常顺序 → 输出结果
为什么要反转字符串?
数字字符串的左边是高位 (如"123"的1是百位),但手工乘法是从个位(低位)开始算 的.
反转后,字符串下标0对应个位、1对应十位、2对应百位... 完美匹配乘法的位运算规则.
为什么临时数组 tmp 长度是 m+n-1?
两个长度为m、n的数字相乘:
- 最小位数:
m + n - 1(如10*10=100,2位+2位=3位=2+2-1) - 最大位数:
m + n(如99*99=9801,2位+2位=4位=2+2)
tmp用m+n-1足够存储无进位的乘积和,最后进位会自动补全最大位数.
核心:tmp[i+j] += 乘积
这是竖式乘法的本质:
- n1的第
i位(低位)× n2的第j位(低位) - 结果一定落在第
i+j位(个位×个位=个位,个位×十位=十位,以此类推)
进位处理逻辑
tmp里存的是无进位的累加值(可能大于10,如12),需要统一进位:
t % 10:取当前位的最终数字(如12→2)t / 10:把进位留给高位(如12→进位1)
前导零处理
特殊情况:"0" × "1234",结果会是"0000",需要保留1个0,删除多余前导零.
举个例子
计算 n1="12" × n2="34"
- 反转 :n1→
"21",n2→"43" - 无进位累加 :tmp长度=2+2-1=3
- i=0,j=0:2×4=8 → tmp[0]=8
- i=0,j=1:2×3=6 → tmp[1]=6
- i=1,j=0:1×4=4 → tmp[1]=6+4=10
- i=1,j=1:1×3=3 → tmp[2]=3
最终 tmp =[8, 10, 3]
- 处理进位 :
- cur=0:t=8 → 存8,进位0
- cur=1:t=10 → 存0,进位1
- cur=2:t=3+1=4 → 存4,进位0
结果字符串 ret ="804"
- 去零+反转 :无前置零,反转→
"408"(正确结果:12×34=408)
核心代码
cpp
class Solution
{
public:
string multiply(string n1, string n2)
{
int m = n1.size(), n = n2.size();
reverse(n1.begin(),n1.end());
reverse(n2.begin(),n2.end());
vector<int> tmp(m+n-1);
//1.⽆进位相乘后相加
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
tmp[i + j] += (n1[i] - '0') * (n2[j] - '0');
//2.处理进位
int cur = 0, t = 0;
string ret;
while (cur < m + n - 1 || t != 0)
{
if (cur < m + n - 1)
t += tmp[cur++];
ret+=t%10+'0';
t /= 10;
}
//3. 处理进位
while (ret.size() > 1 && ret.back()== '0') ret.pop_back();
reverse(ret.begin(),ret.end());
return ret;
}
};
完整测试代码
cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class Solution
{
public:
string multiply(string n1, string n2)
{
int m = n1.size(), n = n2.size();
reverse(n1.begin(),n1.end());
reverse(n2.begin(),n2.end());
vector<int> tmp(m+n-1);
//1. 无进位相乘后相加
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
tmp[i + j] += (n1[i] - '0') * (n2[j] - '0');
//2. 处理进位
int cur = 0, t = 0;
string ret;
while (cur < m + n - 1 || t != 0)
{
if (cur < m + n - 1)
t += tmp[cur++];
ret += t % 10 + '0';
t /= 10;
}
//3. 去除前导零
while (ret.size() > 1 && ret.back() == '0')
ret.pop_back();
reverse(ret.begin(), ret.end());
return ret;
}
};
int main() {
Solution sol;
string s1 = "12", s2 = "34";
cout << s1 << " * " << s2 << " = " << sol.multiply(s1, s2) << endl;
string s3 = "0", s4 = "1234";
cout << s3 << " * " << s4 << " = " << sol.multiply(s3, s4) << endl;
string s5 = "0", s6 = "0";
cout << s5 << " * " << s6 << " = " << sol.multiply(s5, s6) << endl;
string s7 = "5", s8 = "7";
cout << s7 << " * " << s8 << " = " << sol.multiply(s7, s8) << endl;
string s9 = "999", s10 = "999";
cout << s9 << " * " << s10 << " = " << sol.multiply(s9, s10) << endl;
string s11 = "1234", s12 = "5678";
cout << s11 << " * " << s12 << " = " << sol.multiply(s11, s12) << endl;
return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容:【优选算法】(实战:栈、队列、优先级队列高频考题通关全解)
每日心灵鸡汤:做自己,而不是解释自己!
你没有必要不停地向别人说其实我是怎样的人,因为这是无效的,人们只愿意看到他们愿意看到的你,不用在意这样的话语、那样的眼光,没有必要让所有人都知道真实的你.干掉标准答案,你就是标准答案,因为相信你的人自然相信你,而不相信你的人百口莫辩,你的时间就那么长,外界的声音只是参考,不喜欢就不参考.你就是你,他人就是他人,请好好爱自己,尊重理解自己.大风刮倒梧桐树,自有旁人论长短.
