541. 反转字符串II
方法一:模拟+双指针
用时:37m27s
思路
自己想的一个方法,其实想复杂了
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
string reverseStr(string s, int k) {
int size = s.size();
int left = 0;
int right = 0;
int count = 1;
for (; count <= size; ++count) {
if (count % (2 * k) == 0) {
left = count - 2 * k;
right = count - k - 1;
while (left < right) swap(s[left++], s[right--]);
}
}
--count;
left = count - (count % (2 * k));
right = min(left + k - 1, size - 1);
while (left < right) swap(s[left++], s[right--]);
return s;
}
};
方法二:模拟
思路
遍历字符串,每次向前走2k步,翻转字符串i至i+k的字符,若i+k超出了字符串的范围,则把剩余的字符串全部翻转。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
string reverseStr(string s, int k) {
int size = s.size();
for (int i = 0; i < size; i += 2 * k) {
if (i + k - 1 < size) reverse(s.begin() + i, s.begin() + i + k);
else reverse(s.begin() + i, s.end());
}
return s;
}
};
看完讲解的思考
一开始想复杂了,被题目带偏了,先往前走计数再回头交换位置。更简单的做法是先交换位置,再往前走。
代码实现遇到的问题
无。
剑指Offer 05. 替换空格
方法一:遍历
思路
最朴素的解法,新建一个字符串然后遍历赋值。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
string replaceSpace(string s) {
string res;
for (char& c : s) {
if (c != ' ') res += c;
else res.append("%20");
}
return res;
}
};
方法二:双指针原地修改
用时:8m9s
思路
首先遍历一遍字符串计算空格的数量,然后延长字符串,每有一个空格就新增两个字符。然后双指针从后往前赋值。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
string replaceSpace(string s) {
int oriIdx = s.size() - 1;
int count = 0;
// 记录空格的数量
for (char& c : s) {
if (c == ' ') ++count;
}
// 每个空格需要新增两个字符
while (count) {
s += " ";
--count;
}
int newIdx = s.size() - 1;
while (oriIdx >= 0) {
if (s[oriIdx] != ' ') s[newIdx--] = s[oriIdx];
else {
s[newIdx--] = '0';
s[newIdx--] = '2';
s[newIdx--] = '%';
}
--oriIdx;
}
return s;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
151. 翻转字符串里的单词
方法一:模拟
思路
从后向前遍历,将单词放到一个新的字符串中。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
string reverseWords(string s) {
int size = s.size();
int left = 0;
int right = 1;
// 翻转每一个单词,并且将空格翻转到字符串尾端
while (right < size) {
if (s[right] == ' ' && s[right - 1] != ' ') {
reverse(s.begin() + left, s.begin() + right);
while (s[left] != ' ' && left < right) ++left;
++left;
++right;
} else ++right;
}
reverse(s.begin() + left, s.end());
// 去除字符串尾部的空格
while (s[s.size() - 1] == ' ') s.erase(s.size() - 1);
// 整体翻转
reverse(s.begin(), s.end());
return s;
}
};
方法二:原地修改-先局部翻转再全局翻转
用时:34m1s
思路
自己想的一个方法,把每一个单词以及空格局部翻转,通过多次局部翻转可以把多余的空格全部移动至字符串末端,然后将空格删除,最后再将整个字符串翻转。
思路有点类似于冒泡排序,多余的空格通过不断地翻转,位置逐渐右移直至末端。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
string reverseWords(string s) {
int size = s.size();
int left = 0;
int right = 1;
while (right < size) {
if (s[right] == ' ' && s[right - 1] != ' ') {
reverse(s.begin() + left, s.begin() + right);
while (s[left] != ' ' && left < right) ++left;
++left;
++right;
} else ++right;
}
reverse(s.begin() + left, s.end());
while (s[s.size() - 1] == ' ') s.erase(s.size() - 1);
reverse(s.begin(), s.end());
return s;
}
};
方法三:原地修改-先全局翻转再局部翻转
用时:4m29s
思路
先翻转整个字符串,然后再用双指针将每个单词每间隔一个空格赋值到字符串前端,再将该单词局部翻转。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
string reverseWords(string s) {
int size = s.size();
int write = 0;
// 整体翻转
reverse(s.begin(), s.end());
// 将单词每空一格然后以至字符串前端并翻转
for (int start = 0; start < size; ++start) {
if (s[start] != ' ') {
// 每个单词间隔一个空格
if (write != 0) s[write++] = ' ';
// start为当前单词开始的位置,end为当前单词结束的位置
int end = start;
while (end < size && s[end] != ' ') s[write++] = s[end++];
// 赋值到字符串前端后,再将单词翻转
reverse(s.begin() + write - (end - start), s.begin() + write);
start = end;
}
}
// write之后的字符都删除掉
s.erase(s.begin() + write, s.end());
return s;
}
};
看完讲解的思考
字符串局部翻转+全局翻转可以实现调整子串顺序的功能。
例如:abcd efgh
先分别翻转前四个字符和后四个字符:dcba hgfe
再整体翻转:efgh abcd
就能实现前一半子串和后一半子串顺序调换。
代码实现遇到的问题
无。
剑指offer 58. 左旋转字符串II
方法一:模拟
思路
新建一个字符串,然后按照规则得到给新的字符串赋值。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
string reverseLeftWords(string s, int n) {
int size = s.size();
string res(s);
for (int i = 0; i < n; ++i) res[size - n + i] = s[i];
for (int i = n; i < size; ++i) res[i - n] = s[i];
return res;
}
};
方法二:原地修改
用时:1m19s
思路
先将前段字符串翻转,再将后段字符串翻转,最后将整个字符串翻转。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
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. 找出字符串中第一个匹配项的下标
方法一:暴力解法
用时:8m12s
思路
- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
int strStr(string haystack, string needle) {
int size1 = haystack.size();
int size2 = needle.size();
for (int i = 0; i < size1; ++i) {
if (haystack[i] == needle[0]) {
bool flag = true;
for (int j = 0; j < size2; ++j) {
if (haystack[i + j] != needle[j]) {
flag = false;
break;
}
}
if (flag) return i;
}
}
return -1;
}
};
方法二:KMP算法
用时:1h16m23s
思路
-
next数组:当前位置之前的子串的最长相等前后缀长度。
例如:ababaaindex 0 1 2 3 4 5 str a b a b a a next -1 0 0 1 2 3 - next[0]表示str[0]之前的子串的最长相等前后缀长度,由于str[0]之前不存在字符串,所以设置为-1,即next[0] = -1。
- next[1]表示str[1]之前的子串“a”的最长相等前后缀长度,由于“a”只有一个字符,没有前后缀,所以最长相等前后缀长度为0, 即next[1] = 0。
- next[2]表示str[2]之前的子串“ab”的最长相等前后缀长度,“ab”最长相等前后缀长度为0, 即next[2] = 0。
- next[3]表示str[3]之前的子串“aba”的最长相等前后缀长度,“aba”最长相等前后缀长度为1(前缀“a”和后缀“a”), 即next[3] = 1。
- next[4]表示str[4]之前的子串“abab”的最长相等前后缀长度,“abab”最长相等前后缀长度为2(前缀“ab”和后缀“ab”), 即next[4] = 2。
- next[5]表示str[5]之前的子串“ababa”的最长相等前后缀长度,“ababa”最长相等前后缀长度为3(前缀“aba”和后缀“aba”), 即next[5] = 3。
-
得到next数组后,遍历主串中的字符和模式串中的字符进行比对,若不满足则根据next数组回退到上一个要比对的地方,直至模式串开头。
- 时间复杂度: O ( m + n ) O(m+n) O(m+n),m是主串的长度,n是模式串的长度。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
int strStr(string haystack, string needle) {
int size1 = haystack.size();
int size2 = needle.size();
if (size2 == 0) return 0;
vector<int> next = getNext(needle);
int i = 0;
int j = 0;
while (i < size1 && j < size2) {
if (haystack[i] == needle[j]) {
++i;
++j;
} else {
j = next[j];
if (j == -1) {
j = 0;
++i;
}
}
}
return j >= size2 ? i - size2 : -1;
}
vector<int> getNext(string& s) {
int size = s.size();
if (size == 0) return {};
else if (size == 1) return {-1};
else if (size == 2) return {-1, 0};
vector<int> next(size, 0);
next[0] = -1;
next[1] = 0;
for (int i = 2; i < size; ++i) {
if (s[i - 1] == s[next[i - 1]]) next[i] = next[i - 1] + 1;
else {
int cur = next[next[i - 1]];
while (cur != -1 && s[i - 1] != s[cur]) cur = next[cur];
next[i] = cur + 1;
}
}
return next;
}
};
方法三:KMP算法优化
用时:
思路
-
nextVal数组:next数组的优化版本。在匹配的过程中,当某个位置的字符不匹配时,根据next数组回退到另外一个位置的字符,例如str[2]不匹配,根据next[2]回退到str[0],但是str[2]和str[0]是相等的,如果str[2]不匹配则str[0]也是不匹配的,此处可以进行优化。
当str[i] == str[next[i]]
时,nextVal[i] = next[next[i]]
。
例如:ababaaindex 0 1 2 3 4 5 str a b a b a a next -1 0 0 1 2 3 nextVal -1 0 -1 0 -1 3 -
得到nextVal数组后,遍历主串中的字符和模式串中的字符进行比对,若不满足则根据next数组回退到上一个要比对的地方,直至模式串开头。
- 时间复杂度: O ( m + n ) O(m+n) O(m+n),m是主串的长度,n是模式串的长度。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
int strStr(string haystack, string needle) {
int size1 = haystack.size();
int size2 = needle.size();
if (size2 == 0) return 0;
vector<int> next = getNextVal(needle);
int i = 0;
int j = 0;
while (i < size1 && j < size2) {
if (haystack[i] == needle[j]) {
++i;
++j;
} else {
j = next[j];
if (j == -1) {
j = 0;
++i;
}
}
}
return j >= size2 ? i - size2 : -1;
}
vector<int> getNext(string& s) {
int size = s.size();
if (size == 0) return {};
else if (size == 1) return {-1};
else if (size == 2) return {-1, 0};
vector<int> next(size, 0);
next[0] = -1;
next[1] = 0;
for (int i = 2; i < size; ++i) {
if (s[i - 1] == s[next[i - 1]]) next[i] = next[i - 1] + 1;
else {
int cur = next[next[i - 1]];
while (cur != -1 && s[i - 1] != s[cur]) cur = next[cur];
next[i] = cur + 1;
}
}
return next;
}
vector<int> getNextVal(string& s) {
vector<int> next = getNext(s);
int size = s.size();
for (int i = 0; i < size; ++i) {
if (next[i] != -1 && s[i] == s[next[i]]) {
next[i] = next[next[i]];
}
}
return next;
}
};
看完讲解的思考
KMP算法好难啊,之后得多敲几遍。
代码实现遇到的问题
无。
最后的碎碎念
前段时间放假去了,断断续续的刷了几题,还都没完全刷完,今天把之前的坑填了填。KMP算法花了好多时间,虽然之前学过了,但是过一段时间又忘了,看来还是需要多敲几遍。