训练营第九天继续打卡,今天的内容还是做了挺久的。第1题删除多余空格的操作想了比较久,然后看了下KMP还是没很懂,后面再看。
151.翻转字符串中的单词
解题过程
- 若不用库函数,全部自己实现功能,则可以分三步:第一步删掉多余空格,这一步可以参考力扣27题(移除元素);第二步反转所有字符串;第三步逐个单词反转
- 这次把删除多余空格实现出来了,但是后面两步又没写出来,这题还是比较难写的,后面还会再来做这一题
知识点
- 使用erase移除空格的时间复杂度为O(n^2),所以采用双指针移除元素
- 时间复杂度: O(n)
- 空间复杂度: O(1)
整体代码
class Solution {
public:
void deleteSpace(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);
}
void myReverse(string& s, int leftIndex, int rightIndex) {
while (leftIndex < rightIndex) swap(s[leftIndex++], s[rightIndex--]);
}
string reverseWords(string s) {
deleteSpace(s);
myReverse(s, 0, s.size() - 1);
int start = 0;
for (int i = 0; i <= s.size(); i++) {
if (i == s.size() || s[i] == ' ') {
myReverse(s, start, i - 1);
start = i + 1;
}
}
return s;
}
};
55.卡码网【右旋字符串】
解题过程
- 三次分区间反转即可完成右旋字符串
知识点
- 左旋的话,先旋转局部再旋转全局比较方便
右旋字符串
#include<iostream>
#include<string>
using namespace std;
void myReverse(string& s, int left, int right) {
while (left < right) {
swap(s[left++], s[right--]);
}
}
int main() {
int k;
char c;
string str;
cin >> k;
while (cin >> c) str += c;
myReverse(str, 0, str.size() - 1);
myReverse(str, 0, k - 1);
myReverse(str, k, str.size() - 1);
for (char c : str) cout << c;
cout << endl;
return 0;
}
28.找出字符串中第一个匹配项的下标
解题过程
- 知道本题可以用KMP做,但是还是选择了暴力法,KMP等有时间再细看
知识点
- KMP时间复杂度: O(n + m),空间复杂度: O(m)
- 暴力时间复杂度: O(n * m),空间复杂度: O(1)
暴力法
class Solution {
public:
int strStr(string haystack, string needle) {
for (int i = 0; i < haystack.size(); i++) {
if (haystack[i] == needle[0]) {
int j;
for (j = 0; j < needle.size(); j++) {
if (haystack[i + j] != needle[j]) break;
}
if (j == needle.size()) return i;
}
}
return -1;
}
};
459.重复的子字符串
解题过程
- 本题可用KMP,但理论比较麻烦,就没做这个方法
- 用暴力法也没做出来,看到有移动匹配的方法感觉不错,参考一下
知识点
- 移动匹配的时间复杂度: O(1)空间复杂度: O(1)
- string中find()返回值是字母在母串中的位置(下标记录),如果没有找到,那么会返回一个特别的标记npos。(返回值可以看成是一个int型的数)
- 也可以查找某一给定位置后的子串的位置
- 此代码由大力王提供,太厉害了,下面是Yves的解析:
- 如果一个字符串可以由多个重复子串构成,即具有循环节 设最小循环节用a来表示,他代表通过子串a重复多次可以构成s 即s换成a来表示就是aa···aaa,由多少个最小循环节a构成s,那么就有几个a
- 找循环节一个一个对比比较麻烦,最简单方法就是s+s就可以直接增加多一倍的循环节
- 假设原来s=aaaa,那ss=s+s=aaaa aaaa 因为是不断重复的循环节,可以通过简单的屏蔽的第一个字符,然后再在ss中寻找s 因为屏蔽第一个字符,即第一个最小循环节被破坏,所以找到的s应该是从第二个循环节开始
- 但倘若不是由一个子串重复构成 即s=abcd,那ss=abcd abcd=s+s 屏蔽掉第一个字符,又因不匹配,所以在ss中寻找s,一定是对应着新增s的位置,即s.size()处
class Solution {
public:
bool repeatedSubstringPattern(string s) {
return (s + s).find(s, 1) != s.size();
}
};
字符串总结
字符串理论基础
- 在C语言中,把一个字符串存入一个数组时,也把结束符 '\0’存入数组,并以此作为该字符串是否结束的标志。
- 在C++中,提供一个string类,string类会提供 size接口,可以用来判断string类字符串是否结束,就不用’\0’来判断是否结束。
- vector< char > 和 string 在基本操作上没有区别,但是 string提供更多的字符串处理的相关接口,例如string 重载了+,而vector却没有。所以想处理字符串,我们还是会定义一个string类型。
字符串经典题目
-
双指针法
- 344.反转字符串
- 我们使用双指针法实现了反转字符串的操作,双指针法在数组,链表和字符串中很常用。
- 替换数字
- 很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
- 27. 移除元素和151.翻转字符串里的单词
- 使用双指针法进行原地移除操作
- 344.反转字符串
-
反转系列
- 541.反转字符串
- 当需要固定规律一段一段去处理字符串的时候,在for循环的表达式上移动区间长度的指针
- 151.翻转字符串里的单词
- 先整体反转再局部反转
- 右旋字符串
- 通过先局部反转再整体反转达到了左旋的效果
- 541.反转字符串
-
KMP
- 不是很懂就略过了
双指针总结
数组篇
- 27. 移除元素
- 通过两个指针在一个for循环下完成两个for循环的工作。
字符串篇
- 344.反转字符串
- 使用双指针法,定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
- 替换数字
- 首先扩充数组到每个空格替换成char之后的大小。然后双指针从后向前替换空格。
- 很多数组(字符串)填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
- 151.翻转字符串里的单词
- 删除冗余空格的过程中,如果不注意代码效率,很容易写成了O(n^2)的时间复杂度。其实使用双指针法O(n)就可以搞定。
- 删除空格后就可以分区间反转
链表篇
- 206.反转链表
- 使用双指针法来翻转链表,只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表。
- 142.环形链表II
- 使用快慢指针(双指针法),分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
- 找到环的入口,需要数学推理,链接里有推理过程
N数之和