字符串是算法面试的高频核心考点,核心考察字符遍历、边界处理、进制运算模拟、回文特性利用等能力。本文通过4道经典字符串题目,拆解不同场景下的解题技巧,涵盖公共前缀查找、回文子串求解、二进制加法、大数乘法等核心场景。
一、最长公共前缀
题目描述:
编写一个函数查找字符串数组中的最长公共前缀,若不存在公共前缀则返回空字符串 ""。
示例:
- 输入:
strs = ["flower","flow","flight"],输出:"fl" - 输入:
strs = ["dog","racecar","car"],输出:""
解题思路:
以第一个字符串为基准,逐字符对比所有字符串的对应位置:
- 遍历第一个字符串的每个字符(索引
i),取该字符作为基准字符tmp。 - 遍历数组中其他所有字符串:
- 若某字符串的索引
i超出自身长度(如"flow"比"flower"短),说明公共前缀截止到i-1; - 若某字符串索引
i的字符与tmp不同,同样截止到i-1;
- 若某字符串的索引
- 若所有字符都匹配,直接返回第一个字符串。
完整代码:
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() || strs[j][i] != tmp)
return strs[0].substr(0, i);
}
return strs[0];
}
};
复杂度分析:
- 时间复杂度:O(nm)O(nm)O(nm),
n是字符串数组长度,m是最长公共前缀的长度(最坏情况遍历所有字符串的所有字符)。 - 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。
二、最长回文子串
题目描述:
给定字符串 s,找到其中最长的回文子串(回文:正读和反读相同的字符串)。
示例:
- 输入:
s = "babad",输出:"bab"(或"aba") - 输入:
s = "cbbd",输出:"bb"
解题思路:
中心扩展法(回文的核心特性:以单个字符为中心(奇数长度)或两个相同字符为中心(偶数长度)向两侧扩展):
- 遍历字符串的每个字符,分别处理两种情况:
- 以
i为中心(奇数长度回文):left = i,right = i; - 以
i和i+1为中心(偶数长度回文):left = i,right = i+1;
- 以
- 向两侧扩展,直到左右字符不相等,记录当前回文子串的长度和起始位置。
- 遍历结束后,返回最长的回文子串。
完整代码:
cpp
class Solution {
public:
string longestPalindrome(string s) {
int len = 0, begin = 0;
for(int i = 0; i < s.size(); i++)
{
// 奇数长度回文
int left = i, right = i;
while(left >= 0 && right <= s.size() && s[left] == s[right])
{
left--;
right++;
}
if(right - left - 1 > len)
{
len = right - left - 1;
begin = left + 1;
}
// 偶数长度回文
left = i, right = i + 1;
while(left >= 0 && right <= s.size() && s[left] == s[right])
{
left--;
right++;
}
if(right - left - 1 > len)
{
len = right - left - 1;
begin = left + 1;
}
}
return s.substr(begin, len);
}
};
复杂度分析:
- 时间复杂度:O(n2)O(n^2)O(n2),
n是字符串长度,每个字符最多向两侧扩展 O(n)O(n)O(n) 次。 - 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。
三、二进制求和
题目描述:
给定两个二进制字符串 a 和 b,返回它们的和(以二进制字符串形式)。
示例:
- 输入:
a = "11", b = "1",输出:"100" - 输入:
a = "1010", b = "1011",输出:"10101"
解题思路:
模拟手工二进制加法过程(从后往前遍历,处理进位):
- 用
t存储进位,cur1/cur2分别指向两个字符串的末尾(最低位)。 - 遍历过程中累加当前位的值和进位:
- 若
cur1/cur2未越界,将对应字符转为数字累加到t; - 当前位结果为
t % 2(二进制取余),新的进位为t / 2;
- 若
- 遍历结束后反转结果字符串(因拼接时是从低位到高位)。
完整代码:
cpp
class Solution {
public:
string addBinary(string a, string b) {
int t = 0;
string ret;
int cur1 = a.size() - 1, cur2 = b.size() - 1;
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;
}
};
复杂度分析:
- 时间复杂度:O(max(n,m))O(\max(n,m))O(max(n,m)),
n/m是两个字符串的长度,遍历次数为较长字符串的长度。 - 空间复杂度:O(1)O(1)O(1)(结果字符串为必要输出,不计入额外复杂度)。
四、字符串相乘
题目描述:
给定两个以字符串形式表示的非负整数 num1 和 num2,返回它们的乘积(同样以字符串形式表示)。
示例:
- 输入:
num1 = "2", num2 = "3",输出:"6" - 输入:
num1 = "123", num2 = "456",输出:"56088"
解题思路:
模拟大数乘法的手工计算过程(反转字符串简化低位计算,数组存储中间乘积):
- 反转两个字符串,方便从低位(原字符串末尾)开始计算。
- 用数组
tmp存储每一位的乘积结果:num1[i] * num2[j]的结果对应tmp[i+j](手工乘法中,个位乘个位对应结果的个位,个位乘十位对应结果的十位)。 - 处理数组的进位,拼接结果字符串,反转后返回(注意去除末尾多余的0)。
完整代码:
cpp
class Solution {
public:
string multiply(string num1, string num2) {
int n = num1.size(), m = num2.size();
reverse(num1.begin(), num1.end());
reverse(num2.begin(), num2.end());
vector<int> tmp(m + n - 1);
// 计算每一位的乘积
for(int i = 0; i < num1.size(); i++)
for(int j = 0; j < num2.size(); j++)
tmp[i + j] += (num1[i] - '0') * (num2[j] - '0');
// 处理进位
int t = 0, cur = 0;
string ret;
while(cur < m + n - 1 || t)
{
if(cur < m + n - 1) t += tmp[cur++];
ret += (t % 10) + '0';
t /= 10;
}
// 去除末尾的0(避免结果为"0000"的情况)
while(ret.back() == '0' && ret.size() > 1) ret.pop_back();
reverse(ret.begin(), ret.end());
return ret;
}
};
复杂度分析:
- 时间复杂度:O(nm)O(nm)O(nm),
n/m是两个字符串的长度,双层循环计算乘积。 - 空间复杂度:O(n+m)O(n+m)O(n+m),数组
tmp的长度为n+m-1,用于存储中间乘积结果。