参考引用:代码随想录
- 注:每道 LeetCode 题目都使用 ACM 代码模式,可直接在本地运行,蓝色字体为题目超链接
344. 反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
- 示例 1
输入:s = [“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]- 示例 2
输入:s = [“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]- 提示
1 <= s.length <= 10^5
s[i] 都是 ASCII 码表中的可打印字符
// 时间复杂度: O(n)
// 空间复杂度: O(1)
#include
#include
using namespace std;
class Solution {
public:
// 使用双指针法来实现反转操作
// 双指针分别指向字符串的首尾位置
// 通过循环,交换指针所指向的字符,并同时向中间移动,直到达到中间位置
void reverseString(vector<char> &s) {
for (int i = 0, j = s.size() - 1; i < s.size() / 2; ++i, --j) {
swap(s[i], s[j]); // 使用 swap 库函数实现交换
}
}
};
int main(int argc, char *argv[]){
vector<char> s {'h', 'e', 'l', 'l', 'o'};
Solution solution;
solution.reverseString(s);
for (char c : s) {
cout << c << " ";
}
cout << endl;
return 0;
}
541. 反转字符串II
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
1、如果剩余字符少于 k 个,则将剩余字符全部反转。
2、如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样
- 示例 1
输入:s = “abcdefg”, k = 2
输出:“bacdfeg”- 示例 2
输入:s = “abcd”, k = 2
输出:“bacd”- 提示
1 <= s.length <= 10^4
s 仅由小写英文组成
1 <= k <= 10^4
// 时间复杂度: O(n)
// 空间复杂度: O(1)
#include
#include
using namespace std;
class Solution {
public:
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. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= s.size()) {
reverse(s, i, i + k - 1);
continue;
}
// 3. 剩余字符少于 k 个,则将剩余字符全部反转
reverse(s, i, s.size() - 1);
}
return s;
}
};
int main(int argc, char *argv[]){
string s = "abcdefg";
int k = 2;
Solution solution;
cout << solution.reverseStr(s, k) << endl;
return 0;
}
剑指 Offer 05. 替换空格
请实现一个函数,把字符串 s 中的每个空格替换成"%20"
- 示例 1
输入:s = “We are happy.”
输出:“We%20are%20happy.”- 提示
0 <= s 的长度 <= 10000
// 时间复杂度:O(n)
// 空间复杂度:O(1)
#include
#include
using namespace std;
class Solution {
public:
string replaceSpace(string s) {
int count = 0; // 记录字符串 s 中空格的数量
int sOldSize = s.size(); // 记录原始字符串 s 的长度
// 遍历字符串 s 中的每个字符,检查是否为 ' '(空格)
for (int i = 0; i < s.size(); ++i) {
if (s[i] == ' ') {
count++;
}
}
// 扩充字符串 s 的大小,也就是每个空格替换成 "%20" 之后的大小
s.resize(s.size() + count * 2);
int sNewSize = s.size(); // 更新字符串 s 的新大小
// 从字符串末尾开始,逐个复制原始字符串 s 的字符到新字符串 s 中
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; --i, --j) {
if (s[j] != ' ') { // 如果当前字符不是空格,则直接将其复制到新字符串 s 中
s[i] = s[j];
} else { // 如果当前字符是空格,则用字符串 %20 替换空格
s[i] = '0';
s[i - 1] = '2';
s[i - 2] = '%';
i -= 2; // 将 i 左移两个位置,以便继续插入下一个 %20 字符串
}
}
return s;
}
};
int main(int argc, char *argv[]) {
string s = "We are happy.";
Solution solution;
cout << solution.replaceSpace(s) << endl;
return 0;
}
char a[5] = "asd";
for (int i = 0; a[i] != '\0'; i++) {
}
string a = "asd";
for (int i = 0; i < a.size(); i++) {
}
151. 反转字符串中的单词
给你一个字符串 s ,请你反转字符串中单词的顺序。单词是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的单词分隔开。返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
- 示例 1
输入:s = “the sky is blue”
输出:“blue is sky the”- 示例 2
输入:s = " hello world "
输出:“world hello”
解释:反转后的字符串中不能存在前导空格和尾随空格- 示例 3
输入:s = “a good example”
输出:“example good a”
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个- 提示
1 <= s.length <= 10^4
s 包含英文大小写字母、数字和空格 ’ ’
s 中 至少存在一个单词
// 时间复杂度: O(n)
// 空间复杂度: O(1)
#include
#include
using namespace std;
class Solution {
public:
// 同 反转字符串
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; // 整体思想参考 27.移除元素
for (int i = 0; i < s.size(); ++i) {
if (s[i] != ' ') { // 遇到非空格就处理,即删除所有空格
// 手动控制空格,给单词之间添加空格
// slow != 0 说明不是第一个单词,需要在单词前添加空格
if (slow != 0) {
s[slow] = ' ';
slow++;
}
// 补上该单词,遇到空格说明单词结束
while (i < s.size() && s[i] != ' ') {
s[slow++] = s[i++];
}
}
}
s.resize(slow); // slow 的大小即为去除多余空格后的大小
}
string reverseWords(string s) {
// 去除多余的空格,保证单词之间之只有一个空格,且字符串首尾没空格
removeExtraSpaces(s);
reverse(s, 0, s.size() - 1); // 对整个字符串进行翻转
// 去除多余的空格后保证第一个单词的开始下标一定是 0
int start = 0;
for (int i = 0; i <= s.size(); ++i) {
// 遍历字符串 s,当遇到空格或者遍历到字符串末尾时,说明一个单词结束
// 下一步进行翻转:从 start 到当前位置(不包括当前位置)的字符翻转
// 并更新 start 为下一个单词的起始位置
if (i == s.size() || s[i] == ' ') {
reverse(s, start, i - 1);
start = i + 1;
}
}
return s;
}
};
int main(int argc, char *argv[]) {
Solution solution;
string s = "a good example";
cout << solution.reverseWords(s) << endl;
return 0;
}
剑指 Offer 58 - II. 左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串 “abcdefg” 和数字 2,该函数将返回左旋转两位得到的结果 “cdefgab”
- 示例 1
输入: s = “abcdefg”, k = 2
输出: “cdefgab”- 示例 2
输入: s = “lrloseumgh”, k = 6
输出: “umghlrlose”- 提示
1 <= k < s.length <= 10000
// 时间复杂度: O(n)
// 空间复杂度:O(1)
#include
#include
#include
using namespace std;
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;
}
};
int main(int argc, char *argv[]) {
string s = "abcdefg";
int n = 2;
Solution solution;
string result = solution.reverseLeftWords(s, n);
cout << result << endl;
return 0;
}
28. 找出字符串中第一个匹配项的下标
实现 strStr() 函数:给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1
- 示例 1
输入:haystack = “sadbutsad”, needle = “sad”
输出:0
解释:“sad” 在下标 0 和 6 处匹配
第一个匹配项的下标是 0 ,所以返回 0- 示例 2
输入:haystack = “leetcode”, needle = “leeto”
输出:-1
解释:“leeto” 没有在 “leetcode” 中出现,所以返回 -1- 提示
1 <= haystack.length, needle.length <= 10^4
haystack 和 needle 仅由小写英文字符组成
当 needle 是空字符串时应当返回 0
本题是 KMP 经典题目
next 数组就是一个前缀表(prefix table)。前缀表有什么作用呢?
要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf,动画如下
前缀表是如何记录的呢?
什么是前缀表?
前缀表要求的就是相同前后缀的长度
- 字符串 a 的最长相等前后缀长度为 0
- 字符串 aa 的最长相等前后缀长度为 1
- 字符串 aaa 的最长相等前后缀长度为 2
下标 5 之前这部分的字符串(也就是字符串 aabaa)的最长相等的前缀和后缀字符串是子字符串 aa,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么找到与其相同的前缀的后面重新匹配就可以了
所以前缀表具有告知当前位置匹配失败,跳到之前已经匹配过的地方的能力
找到的不匹配的位置,那么此时要看它的前一个字符的前缀表的数值是多少。为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。所以要看前一位的前缀表的数值。前一个字符的前缀表的数值是 2,所以把下标移动到下标 2 的位置继续比配
// 时间复杂度: O(n + m)
// 空间复杂度: O(m)
#include
#include
using namespace std;
class Solution {
public:
// 定义一个函数 getNext 来构造 next 数组
void getNext(int *next, const string &s) {
// 1. 初始化:定义两个指针 i 和 j,j 指向前缀末尾,i 指向后缀末尾
int j = -1; // 该实现中前缀表要统一减一的操作
// next[i] 表示 i(包括 i)之前最长相等的前后缀长度(其实就是 j)
// 所以初始化 next[0] = j
next[0] = j;
// 2. 处理前后缀不相同的情况
// 因为 j 初始化为 -1,比较 s[i] 与 s[j+1]
// i=0 时,它左边没有字符,无法与其他字符形成前后缀
// 所以不能计算它的 next 值,故 i 从 1 开始
for (int i = 1; i < s.size(); ++i) {
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同
j = next[j]; // 向前回退
}
// 3. 处理前后缀相同的情况
if (s[i] == s[j + 1]) { // 找到相同的前后缀
++j;
}
next[i] = j; // 将 j(前缀的长度)赋给 next[i]
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
int next[needle.size()];
getNext(next, needle);
// 定义两个下标:j 指向模式串起始位置,i 指向文本串起始位置
int j = -1; // 因为 next 数组里记录的起始位置为 -1
for (int i = 0; i < haystack.size(); ++i) { // i 就从 0 开始
// 因为 j 从 -1 开始的,所以此处是和 j + 1 进行比较
while (j >= 0 && haystack[i] != needle[j + 1]) {
j = next[j]; // j 寻找之前匹配的位置
}
// 匹配,j 和 i 同时向后移动
if (haystack[i] == needle[j + 1]) {
++j; // i 的自增在 for 循环中
}
// j 指向了模式串的末尾,则说明文本串里出现了模式串
if (j == (needle.size() - 1)) {
// 本题要在文本串中找出模式串出现的第一个位置 (从 0 开始)
// 所以返回当前在 文本串 匹配 模式串 的位置 i 减去模式串的长度
// 就是文本串中出现模式串的第一个位置
return (i - needle.size() + 1);
}
}
return -1;
}
};
int main(int argc, char *argv[]) {
string haystack = "aabaabaafa";
string needle = "aabaaf";
Solution solution;
cout << solution.strStr(haystack, needle) << endl;
return 0;
}
// 时间复杂度: O(n + m)
// 空间复杂度: O(m)
#include
#include
using namespace std;
class Solution {
public:
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;
}
}
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;
}
};
int main(int argc, char *argv[]) {
string haystack = "aabaabaafa";
string needle = "aabaaf";
Solution solution;
cout << solution.strStr(haystack, needle) << endl;
return 0;
}
459. 重复的子字符串
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成
- 示例 1
输入: s = “abab”
输出: true
解释: 可由子串 “ab” 重复两次构成- 示例 2
输入: s = “aba”
输出: false- 示例 3
输入: s = “abcabcabcabc”
输出: true
解释: 可由子串 “abc” 重复四次构成。 (或子串 “abcabc” 重复两次构成)- 提示
1 <= s.length <= 10^4
s 由小写英文字母组成
// 时间复杂度: O(n)
// 空间复杂度: O(1)
#include
#include
using namespace std;
class Solution {
public:
bool repeatedSubstringPattern(string s) {
// 将原始字符串 s 拼接在其后面一次,这样 t 中包含了两个 s
string t = s + s;
// 从 t 的开头和结尾分别删除了一个字符
// 这是为了去除 t 中包含的重复部分,也就是第一个 s
t.erase(t.begin());
t.erase(t.end() - 1);
// 在 t 中查找第一个出现的 s,如果找到了
// 则说明原始字符串 s 是由重复的子串构成的
// std::string::npos 是一个特殊的值,表示在字符串中没有找到匹配项
if (t.find(s) != std::string::npos) {
return true;
}
return false;
}
};
int main(int argc, char *argv[]) {
string s = "abcabcabcabc";
Solution solution;
// boolalpha 功能:把 bool 值显示为 true 或 false
cout << boolalpha << solution.repeatedSubstringPattern(s) << endl;
return 0;
}
// 时间复杂度: O(n)
// 空间复杂度: O(n)
#include
#include
using namespace std;
class Solution {
public:
void getNext (int* next, const string& s){
next[0] = -1;
int j = -1;
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) {
if (s.size() == 0) {
return false;
}
int next[s.size()];
getNext(next, s);
int len = s.size();
// 如果 next[len - 1] != -1,则说明字符串有最长相同的前后缀
// 如果 len % (len - (next[len - 1] + 1)) == 0 ,则说明数组的长度正好可
// 以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串
if (next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0) {
return true;
}
return false;
}
};
int main(int argc, char *argv[]) {
string s = "abcabcabcabc";
Solution solution;
// boolalpha 功能:把 bool 值显示为 true 或 false
cout << boolalpha << solution.repeatedSubstringPattern(s) << endl;
return 0;
}
// 时间复杂度: O(n)
// 空间复杂度: O(n)
#include
#include
using namespace std;
class Solution {
public:
void getNext (int* next, const string& s){
next[0] = 0;
int j = 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;
}
}
bool repeatedSubstringPattern (string s) {
if (s.size() == 0) {
return false;
}
int next[s.size()];
getNext(next, s);
int len = s.size();
if (next[len - 1] != 0 && len % (len - (next[len - 1] )) == 0) {
return true;
}
return false;
}
};
int main(int argc, char *argv[]) {
string s = "abcabcabcabc";
Solution solution;
// boolalpha 功能:把 bool 值显示为 true 或 false
cout << boolalpha << solution.repeatedSubstringPattern(s) << endl;
return 0;
}