字符串反转是算法中最基础但最常考的操作之一。这道题目要求我们原地修改字符数组,意味着不能使用额外的数组空间,只能通过交换字符位置来实现。
双指针法之所以能高效解决这个问题,是因为它同时从字符串的首尾向中间移动,每次交换两个指针所指的字符。这种方法的时间复杂度是O(n/2),也就是O(n),空间复杂度是O(1),完全符合题目要求。
具体实现时需要注意几个关键点:
cpp复制void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
这个版本比原代码更简洁,因为:
提示:在实际面试中,即使题目允许使用reverse函数,也建议先展示手动实现的能力,再提到可以使用STL函数。
这道题目要求每隔2k个字符就反转前k个字符,需要考虑多种边界情况:
解决这个问题的关键在于:
cpp复制void reverse(string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += 2 * k) {
// 情况1:剩余字符≥k个
if (i + k <= s.size()) {
reverse(s, i, i + k - 1);
continue;
}
// 情况2:剩余字符<k个
reverse(s, i, s.size() - 1);
}
return s;
}
关键点解析:
在实际编码中容易犯的错误包括:
调试时可以:
这道题目比简单反转复杂得多,需要处理:
解决这个问题的关键在于分步骤处理:
cpp复制void reverse(string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
void removeExtraSpaces(string& s) {
int slow = 0;
for (int fast = 0; fast < s.size(); ++fast) {
if (s[fast] != ' ') {
if (slow != 0) s[slow++] = ' ';
while (fast < s.size() && s[fast] != ' ') {
s[slow++] = s[fast++];
}
}
}
s.resize(slow);
}
string reverseWords(string s) {
removeExtraSpaces(s);
reverse(s, 0, s.size() - 1);
int start = 0;
for (int i = 0; i <= s.size(); ++i) {
if (i == s.size() || s[i] == ' ') {
reverse(s, start, i - 1);
start = i + 1;
}
}
return s;
}
关键技巧:
优化点:
注意事项:
KMP算法的精髓在于利用部分匹配信息(next数组)避免不必要的比较:
cpp复制void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) {
j = next[j - 1];
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
next数组构建过程:
cpp复制int strStr(string haystack, string needle) {
if (needle.empty()) return 0;
vector<int> next(needle.size());
getNext(&next[0], needle);
int j = 0;
for (int i = 0; i < haystack.size(); i++) {
while(j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == needle.size()) {
return i - needle.size() + 1;
}
}
return -1;
}
匹配过程要点:
时间复杂度:
空间复杂度:O(m),用于存储next数组
关键结论:如果字符串s由重复子串构成,那么s的长度一定是子串长度的整数倍,且s的最长相同前后缀长度与s长度差就是最小重复子串长度。
数学关系:
len % (len - next[len-1]) == 0
cpp复制bool repeatedSubstringPattern(string s) {
if (s.empty()) return false;
int next[s.size()];
getNext(next, s);
int len = s.size();
int n = len - next[len - 1];
return next[len - 1] != 0 && len % n == 0;
}
验证步骤:
需要考虑的特殊情况:
测试用例示例:
在实际开发中,字符串操作是最基础也是最容易出问题的部分。理解这些算法的底层原理,不仅可以帮助我们通过面试,更能写出高效可靠的代码。特别是在处理用户输入、文件解析等场景时,正确的字符串处理方式可以避免很多潜在的安全问题和性能瓶颈。