目录
注意:
如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。
如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。
344.反转字符串
class Solution {
public:
void reverseString(vector<char>& s) {
for (int i = 0; i < s.size() / 2; i++) {
swap(s[i], s[s.size() - i - 1]);
}
}
};
541.反转字符串Ⅱ
- 当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += 2 * k) {
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k );
} else {
reverse(s.begin() + i, s.end());
}
}
return s;
}
};
剑指Offer 05.替换空格
- 最开始的想法:用一个新字符串一个个重新添加
class Solution {
public:
string replaceSpace(string s) {
string res = "";
for (int i = 0; i < s.length(); i++) {
if (s[i] == ' ') {
res += "%20";
} else {
res += s[i];
}
}
return res;
}
};
- 后续优化:不使用额外空间
- 先根据空格数扩充字符串,再利用双指针将字符串元素后移
class Solution {
public:
string replaceSpace(string s) {
int cnt = 0, oldSize = s.size();
for (int i = 0; i < s.length(); i++) {
if (s[i] == ' ') cnt++;
}
s.resize(s.size() + cnt * 2);
int newSize = s.size();
for (int i = oldSize - 1, j = newSize - 1; i >= 0; i--, j--) {
if (s[i] != ' ') s[j] = s[i];
else {
s[j] = '0';
s[j - 1] = '2';
s[j - 2] = '%';
j -= 2;
}
}
return s;
}
};
151.反转字符串中的单词
- 综合考察了字符串的多种操作
- 一种方法是使用额外的辅助空间
- 还有一种方法是只在原字符串上操作
- 移除多余空格
- 将字符串反转
- 根据空格将每个单词反转
移除空格上可以使用双指针法
void removeExtraSpaces(string& s) {
int slow = 0, fast = 0;
// 移除前缀空格
while (fast < s.size() && s[fast] == ' ') fast++;
for (; fast < s.size(); fast++) {
// 移除中间重复空格
if (s[fast] == ' ' && s[fast] == s[fast - 1]) continue;
else s[slow++] = s[fast];
}
// 对末尾是空格的情况进行处理
if (s[slow - 1] == ' ') s.resize(slow - 1);
else s.resize(slow);
}
简化版(有点难理解)
在遇到空格时,将空格后的单词全部向后移动
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);
}
整体代码
class Solution {
public:
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.begin(), s.begin() + s.size());
int start = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') {
// 找到空格后,将空格前的单词反转
reverse(s.begin() + start, s.begin() + i);
start = i + 1;
} else if (i == s.size() - 1) {
// 反转最后一个单词
reverse(s.begin() + start, s.end());
}
}
return s;
}
};
剑指 Offer 58 - II. 左旋转字符串
- 很自然就就会想到用切片substr做
获取str[n:] 和 str[:n]子串,目标串target = str[:n] + str[n:];
- 自己的思路(双指针)
class Solution {
public:
string reverseLeftWords(string s, int n) {
int nn = s.size();
s.resize(s.size() + n);
for (int i = 0, j = nn; i < n || j < s.size(); i++, j++) {
s[j] = s[i];
}
for (int i = 0, j = n; i < nn; i++,j++) {
s[i] = s[j];
}
s.resize(nn);
return s;
}
};
- 还有一个很巧妙的解法,通过局部反转+整体反转实现
- 反转区间为前n的子串
- 反转区间为n到末尾的子串
- 反转整个字符串
- 代码也巨简洁
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverse(s.begin(), s.begin() + n);
reverse(s.begin() + n, s.end());
reverse(s.begin(), s.end());
return s;
}
};
28. 找出字符串中第一个匹配项的下标
- KMP环节
- i是后缀末尾,j是前缀末尾,也是包括i之前的子串最长相等前后缀的长度
- 一种写法是让前缀表统一减1,这样回退 j = next[j]
class Solution {
public:
void getNext(int *next, string &s) {
int j = -1;
next[0] = j;
for (int i = 1; i < s.size(); i++) {
while (j >= 0 && s[i] != s[j + 1]) j = next[j];
if (s[i] == s[j + 1]) j++;
next[i] = j;
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) return 0;
int next[needle.size()];
getNext(next, needle);
int j = -1;
for (int i = 0; i < haystack.size(); i++) {
while (j >= 0 && haystack[i] != needle[j + 1]) j = next[j];
if (haystack[i] == needle[j + 1]) j++;
if (j == (needle.size() - 1)) return i - needle.size() + 1;
}
return -1;
}
};
- 另一种不减1,回退 j = next[j - 1]
class Solution {
public:
void getNext(int *next, string &s) {
int j = 0;
next[0] = j;
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;
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) return 0;
int next[needle.size()];
getNext(next, 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;
}
};
459. 重复的子字符串
- 移动匹配解法:当s为重复字符串时, s + s组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s。
-
我们将头尾元素删除后在整个字符串中查找s,即可得到结果
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string ss = s + s;
ss.erase(ss.begin());
ss.erase(ss.end() - 1); // 去头尾元素
if (ss.find(s) != string::npos) {
return true;
}
return false;
}
};
- KMP解法:若字符串是重复的,那么利用next数组就可以匹配到最长公共子串
next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时字符串asdfasdfasdf的最长相同前后缀的长度。
(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(asdf)。
class Solution {
public:
void getNext(int *next, string &s) {
int j = -1;
next[0] = j;
for (int i = 1; i < s.size(); i++) {
while (j >= 0 && s[i] != s[j + 1]) j = next[j];
if (s[i] == s[j + 1]) j++;
next[i] = j;
}
}
bool repeatedSubstringPattern(string s) {
int next[s.size()];
getNext(next, s);
int l = s.size();
if (next[l - 1] != -1 && l % (l - next[l - 1] - 1) == 0) {
return true;
}
return false;
}
};