前言
万恶的KMP算法,折磨人的KMP算法。但这次对KMP算法有了新的理解,逐渐理解不用重复匹配匹配过的子串这个概念了。
Leetcode 151 翻转字符串里的单词
题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/
代码随想录题解:代码随想录 (programmercarl.com)
思路:分成三步,去除多余空格,翻转整个字符串,翻转单词。去除多余空格:空格在三个部分出现,头,中,尾。头的需要全删掉,所以我们最简单的判断逻辑就能实现,遍历到第一个字母开始存。中的空格需要多加一步,如果当前字符不为空格且slow!=0(证明已经存过单词了),此时先加一个空格然后再存单词即可,尾部的也很简单,不是字母不存就行。翻转字符串不多讲,昨天已经实现过一次。翻转单词则是先记录初始位置,如果遍历到空格就翻转初始位置到当前位置的字符串,然后更新起始位置到空格下一个字符也就是新单词的开头。
代码:
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 delspace(string &s)
{
int slow=0;
for(int fast=0;fast<s.size();fast++)//类似移除字符中的快慢指针
{
if(s[fast]!=' ')//自动过滤头和尾
{
if(slow!=0)//如果已经存了单词 此时遍历到第二个单词的开头 先存一个空格
{
s[slow]=' ';
slow++;
}
while(fast<s.size()&&s[fast]!=' ')//存入这个单词
{
s[slow]=s[fast];
slow++;
fast++;
}
}
}
s.resize(slow);//记得写
}
string reverseWords(string s) {
delspace(s);
reverse(s,0,s.size()-1);
int start=0;//记录起始位置
for(int i=0;i<=s.size();i++)
{
if(s[i]==' '||i==s.size())//后半句是保证翻转最后一个单词
{
reverse(s,start,i-1);
start=i+1;更新起始位置
}
}
return s;
}
};
Leetcode 28 实现strStr()
题目链接:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
代码随想录题解:代码随想录 (programmercarl.com)
思路:先说KMP,感觉需要记住计算NEXT数组的动图,匹配最长公共前后缀。放弃了,我无法用语言形容这个过程,只能意会,各位看图吧。
class Solution {
public:
void getnext(int *next,string s)
{
int j=0;
next[0]=0;
for(int i=1;i<s.size();i++)
{
if(s[i]==s[j])
{
j++;
next[i]=j;
}
while(j>0&&s[i]!=s[j])
{
j=next[j-1];
}
}
}
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;
}
};
Leetcode 459 重复的字字符串
题目链接:https://leetcode.cn/problems/repeated-substring-pattern/
代码随想录题解:代码随想录 (programmercarl.com)
思路:例如s=abcabc由abc组成,那么如果我们将s+s然后将头尾去掉得到bcabcabcab,可以看出其中必然包括一个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;
}
};
卡码网 右旋字符串
题目链接:55. 右旋字符串(第八期模拟笔试) (kamacoder.com)
代码随想录题解:代码随想录 (programmercarl.com)
思路:和翻转字符串里的单词思路差不多,只不过需要限定翻转范围。第一步翻转整个字符串,第二步按范围翻转字符串的各个部分。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int main() {
int n;
string s;
cin >> n;
cin >> s;
int len = s.size(); //获取长度
reverse(s.begin(), s.end()); // 整体反转
reverse(s.begin(), s.begin() + n); // 先反转前一段,长度n
reverse(s.begin() + n, s.end()); // 再反转后一段
cout << s << endl;
}
C++ 深拷贝与浅拷贝
1.什么是深拷贝,浅拷贝?
每个对象都占有一块内存,在复制对象的过程中,浅拷贝复制出来的对象与原对象共享一块内存。而深拷贝复制出来的对象另开辟了一片内存并占有,不共享原对象的内存空间。
2.如何实现深拷贝,浅拷贝?
浅拷贝一般依靠编译器给出的默认拷贝构造函数和拷贝赋值函数,他们的实现一般就是简单的赋值。而深拷贝则需要我们自己给出拷贝构造函数和拷贝赋值函数,里面必须有动态分配的内存。
3.区别与问题
一般出现在这个对象里面有指针变量时,浅拷贝可以理解为将对象里面的指针变量赋值给一个新的指针变量,两个指针指向同一块内存地址;而深拷贝则将数据复制一份并开辟一块新内存,由一个新的指针指向。如果我们采用浅拷贝,对拷贝过来的对象或者原对象进行增删改等操作,会同时影响两个对象,而且在调用析构函数的时候会对一片内存释放两次内存空间,可能会引发一些意外的问题。
4.拷贝构造函数和拷贝赋值函数
拷贝构造函数一定要用引用传递参数,如果是值传递参数的话会导致递归调用。原理是当我们用值传递给一个函数参数时,会自动调用拷贝构造函数形成一个副本给函数使用,此时这个拷贝构造函数的参数也需要一个值传递,那么又会调用一个拷贝构造函数,就会递归调用,所以必须是引用传参。拷贝赋值函数其实就是对赋值运算符重载,他的参数列表隐含了一个this指针,我们在自己实现拷贝赋值函数的时候必须用这个this指针判断当前对象是否为空,如果不为空需要析构当前对象释放空间,否则也会出现内存泄漏的风险。
总结
KMP算法你罪大恶极,我完全无法用语言描述你运作的过程。操作系统比计网有意思,计网感觉没什么道理,死记硬背。