【优选算法】(实战突破字符串:经典题型与解题模板)


🔥承渊政道: 个人主页
❄️个人专栏: 《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题)


算法思路:解法(⽆进位相乘然后相加,最后处理进位):

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

我们先看整体逻辑:

  1. 反转字符串(让低位对齐,方便从个位开始计算)
  2. 无进位相乘累加(用数组存每一位的乘积和)
  3. 统一处理所有进位(得到每一位的最终数字)
  4. 去除前导零、反转回正常顺序 → 输出结果

为什么要反转字符串?

数字字符串的左边是高位 (如"123"的1是百位),但手工乘法是从个位(低位)开始算 的.

反转后,字符串下标0对应个位、1对应十位、2对应百位... 完美匹配乘法的位运算规则.

为什么临时数组 tmp 长度是 m+n-1?

两个长度为mn的数字相乘:

  • 最小位数:m + n - 1(如10*10=100,2位+2位=3位=2+2-1)
  • 最大位数:m + n(如99*99=9801,2位+2位=4位=2+2)
    tmpm+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"

  1. 反转 :n1→"21",n2→"43"
  2. 无进位累加 :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]
  3. 处理进位
    • cur=0:t=8 → 存8,进位0
    • cur=1:t=10 → 存0,进位1
    • cur=2:t=3+1=4 → 存4,进位0
      结果字符串 ret = "804"
  4. 去零+反转 :无前置零,反转→"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;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!


敬请期待下一篇文章内容:【优选算法】(实战:栈、队列、优先级队列高频考题通关全解)


每日心灵鸡汤:做自己,而不是解释自己!
你没有必要不停地向别人说其实我是怎样的人,因为这是无效的,人们只愿意看到他们愿意看到的你,不用在意这样的话语、那样的眼光,没有必要让所有人都知道真实的你.干掉标准答案,你就是标准答案,因为相信你的人自然相信你,而不相信你的人百口莫辩,你的时间就那么长,外界的声音只是参考,不喜欢就不参考.你就是你,他人就是他人,请好好爱自己,尊重理解自己.大风刮倒梧桐树,自有旁人论长短.

相关推荐
ZhengEnCi21 小时前
S10-蓝桥杯 17822 乐乐的积木塔
算法
贾斯汀玛尔斯21 小时前
每天学一个算法--拓扑排序(Topological Sort)
算法·深度优先
t***54421 小时前
如何配置Orwell Dev-C++使用Clang
开发语言·c++
大龄程序员狗哥21 小时前
第25篇:Q-Learning算法解析——强化学习中的经典“价值”学习(原理解析)
人工智能·学习·算法
exp_add321 小时前
质数相关知识
算法
CoderCodingNo21 小时前
【信奥业余科普】C++ 的奇妙之旅 | 13:为什么 0.1+0.2≠0.3?——解密“爆int”溢出与浮点数精度的底层原理
开发语言·c++
南境十里·墨染春水21 小时前
linux学习进展 线程同步——互斥锁
java·linux·学习
雨奔21 小时前
Kubernetes 联邦 Deployment 指南:跨集群统一管理 Pod
java·容器·kubernetes
小辉同志1 天前
215. 数组中的第K个最大元素
数据结构·算法·leetcode··快速选择
balance_rui1 天前
FreeRTOS
笔记·stm32