28. 找出字符串中第一个匹配项的下标
1.1 暴力匹配法(Brute Force)
class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.size(), m = needle.size();
for (int i = 0; i + m <= n; i++) {
bool flag = true;
for (int j = 0; j < m; j++) {
if (haystack[i + j] != needle[j]) {
flag = false;
break;
}
}
if (flag) {
return i;
}
}
return -1;
}
};
特点:
- 时间复杂度:O(n×m)
- 空间复杂度:O(1)
- 简单直观,但效率低
1.2 KMP算法(无哨兵版 - 推荐)
class Solution {
public:
int strStr(string haystack, string needle) {
if (needle.empty()) return 0;
int n = haystack.size(), m = needle.size();
// 计算next数组
vector<int> next(m, 0);
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && needle[i] != needle[j]) {
j = next[j - 1]; // 关键:循环回退
}
if (needle[i] == needle[j]) {
j++;
}
next[i] = j;
}
// 匹配过程
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = next[j - 1]; // 失配时跳转
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == m) {
return i - m + 1; // 找到匹配
}
}
return -1;
}
};
1.3 KMP算法(有哨兵版)
class Solution {
public:
int strStr(string haystack, string needle) {
if (needle.empty()) return 0;
int n = haystack.size(), m = needle.size();
// 添加哨兵
string s = " " + haystack;
string p = " " + needle;
vector<int> next(m + 1, 0);
// 计算next数组
for (int i = 2, j = 0; i <= m; i++) {
while (j > 0 && p[i] != p[j + 1]) {
j = next[j];
}
if (p[i] == p[j + 1]) {
j++;
}
next[i] = j;
}
// 匹配过程
for (int i = 1, j = 0; i <= n; i++) {
while (j > 0 && s[i] != p[j + 1]) {
j = next[j];
}
if (s[i] == p[j + 1]) {
j++;
}
if (j == m) {
return i - m;
}
}
return -1;
}
};
KMP核心思想总结
算法流程:
- 预处理:计算模式串的next数组
- 匹配:利用next数组在失配时智能跳转
关键理解:
next[i]:前i个字符的最长公共前后缀长度- 失配时 :
j = next[j-1](回退到已匹配部分的前缀之后) - 主串指针不回溯,保证O(n)时间复杂度
151、反转字符串中的单词
2.1 双指针法(最优解)
class Solution {
public:
string reverseWords(string s) {
string ans;
int n = s.size();
for (int i = n - 1; i >= 0;) {
// 跳过尾部空格
while (i >= 0 && s[i] == ' ') {
i--;
}
if (i < 0) break; // 处理全空格情况
int end = i; // 单词结尾位置
// 找到单词开头
while (i >= 0 && s[i] != ' ') {
i--;
}
// 添加空格(非第一个单词)
if (!ans.empty()) {
ans += " ";
}
// 提取单词 [i+1, end]
ans += s.substr(i + 1, end - i);
}
return ans;
}
};
特点:
- 时间复杂度:O(n)
- 空间复杂度:O(1)(不包括结果字符串)
- 原地操作,效率最高
2.2 栈方法
class Solution {
public:
string reverseWords(string s) {
stack<string> stk;
int n = s.size();
int i = 0;
while (i < n) {
// 跳过前导空格
while (i < n && s[i] == ' ') i++;
if (i >= n) break;
// 找到单词结束位置
int start = i;
while (i < n && s[i] != ' ') i++;
// 提取单词
string word = s.substr(start, i - start);
stk.push(word);
}
string ans;
while (!stk.empty()) {
ans += stk.top();
stk.pop();
if (!stk.empty()) ans += ' ';
}
return ans;
}
};
特点:
- 时间复杂度:O(n)
- 空间复杂度:O(n)
- 思路清晰,易于理解
2.3 字符串流方法(最简洁)
class Solution {
public:
string reverseWords(string s) {
stringstream ss(s);
string word, ans;
while (ss >> word) {
if (!ans.empty()) ans = " " + ans;
ans = word + ans;
}
return ans;
}
};
特点:
- 代码最简洁
- 自动处理空格分割
- 时间复杂度:O(n),空间复杂度:O(n)
算法对比总结
KMP算法对比:
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 暴力法 | O(n×m) | O(1) | 简单直观,效率低 |
| KMP无哨兵 | O(n+m) | O(m) | 推荐,易于理解 |
| KMP有哨兵 | O(n+m) | O(m) | 代码简洁,边界处理简单 |
反转单词对比:
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 双指针法 | O(n) | O(1) | 最优解,原地操作 |
| 栈方法 | O(n) | O(n) | 思路清晰,易于理解 |
| 字符串流 | O(n) | O(n) | 代码最简洁 |
核心技巧总结
KMP关键点:
- next数组计算 :
while循环确保完全回退 - 失配跳转 :
j = next[j-1]利用已匹配信息 - 主串指针不回溯:保证线性时间复杂度
反转单词关键点:
- 双指针技巧:一个找单词头,一个找单词尾
- 边界处理:正确处理连续空格和边界情况
- 字符串拼接 :使用
if (!ans.empty())避免开头多余空格