459. 重复的子字符串
方法一:暴力解法
用时:12m55s
思路
遍历每一个前缀子串,判断字符串能否以该子串重复构成。
注意:
- 如果子串的长度大于s长度的一半,那s不可能由该子串重复构成。
- 子串长度若不可以整除s的长度,则肯定不能重复构成s。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int size = s.size();
for (int i = 0; i < size / 2; ++i) {
if (size % (i + 1) == 0) {
bool flag = true;
for (int j = i + 1; j < size; ++j) {
if (s[j] != s[j % (i + 1)]) {
flag = false;
break;
}
}
if (flag) return true;
}
}
return false;
}
};
方法二:字符串匹配巧解
用时:1h10m27s
思路
- 设输入的字符串为
s
,如果s
可以由重复的子串构成,则把s
的子串前缀移到s的后端得到的字符串s'
跟s
是完全一样的(例如:s = abcabcabc
,重复的子串为abc
,将前缀abc
移到后端得到s' = abcabcabc = s
)。
所以如果我们将两个s
拼接到一起,由第一个s
的后端和第二个s
的前端一定能够组成s
。 - 将两个
s
拼接在一起得到ss
,去掉首字符和最后一个字符得到ssStrip
,如果在ssStrip
中仍然存在s
,则说明s
可以由重复的子串构成。如果不掐头去尾的话,ss
中肯定是存在s
的,我们是要判断ss
中间部分是否存在s
,所以需要掐头去尾。 - 代码实现时,字符串匹配直接调用了库函数,也可以用KMP算法实现。
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string ssStrip(s.begin() + 1, s.end());
ssStrip.append(s.begin(), s.end() - 1);
return ssStrip.find(s) == -1 ? false : true;
}
};
方法三:使用next数组求解
用时:7m8s
思路
- 设字符串的长度为 L 1 L_1 L1,字符串的最长相等前后缀的长度为 L 2 L_2 L2,若 L 1 L_1 L1可以被 ( L 1 − L 2 ) (L_1-L_2) (L1−L2)整除,则该字符串可以由重复的子串构成。
- 理由:如图所示,字符串s的
L
1
L_1
L1为8、
L
2
L_2
L2为6,则最长相等后缀不包含的部分为
s[0:2]
,由于t[0]==k[0]==t[2]==k[2]==t[4]==k[4]
,所以s[0]==s[2]==s[4]==s[6]
,同理得s[1]==s[3]==s[5]==s[7]
,即s可以由s[0:2]重复构成。 - 总结:最长相等后缀不包含的子串,当满足第1点时,可以重复构成s。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int size = s.size();
int prefix = 0;
vector<int> next(size, 0);
for (int i = 1; i < size; ++i) {
while (prefix > 0 && s[prefix] != s[i]) prefix = next[prefix - 1];
if (s[i] == s[prefix]) ++prefix;
next[i] = prefix;
}
return next[size - 1] != 0 && size % (size - next[size - 1]) == 0 ? true : false;
}
};
看完讲解的思考
这尼玛是简单题???这题好绕啊。
方法二三都太牛了。
代码实现遇到的问题
无。
232. 用栈实现队列
方法一:模拟(自己想的版本)
用时:17m26s
思路
创建两个栈s1
和s2
。
push
操作:将s2
中的所有元素逐个弹出并压入s1
中,然后再将新的元素压入s1
。pop
和peek
操作:将s1
中的所有元素逐个弹出并压入s2
中,然后再弹出并返回s2
栈顶元素(pop
)或者返回栈顶元素(peek
)。empty
操作:只有当s1
和s2
都为空时才返回true
。- 时间复杂度:当连续使用push时,首次push的时间复杂度为 O ( n ) O(n) O(n),后续的push的时间复杂度为 O ( 1 ) O(1) O(1);当连续使用pop或peek时,首次操作的时间复杂度为 O ( n ) O(n) O(n),后续的时间复杂度为 O ( 1 ) O(1) O(1);empty的时间复杂度为 O ( 1 ) O(1) O(1)。
- 空间复杂度: O ( n ) O(n) O(n)。所有操作都只需要两个栈的空间。
C++代码
class MyQueue {
public:
MyQueue() {
this->s1 = new stack<int>();
this->s2 = new stack<int>();
}
void push(int x) {
while (!this->s2->empty()) {
this->s1->push(this->s2->top());
this->s2->pop();
}
this->s1->push(x);
}
int pop() {
while (!this->s1->empty()) {
this->s2->push(this->s1->top());
this->s1->pop();
}
int val = this->s2->top();
this->s2->pop();
return val;
}
int peek() {
while (!this->s1->empty()) {
this->s2->push(this->s1->top());
this->s1->pop();
}
return this->s2->top();
}
bool empty() {
return this->s1->empty() && this->s2->empty();
}
private:
stack<int>* s1;
stack<int>* s2;
};
方法二:模拟(标准版)
用时:4m17s
思路
方法一是自己想的,还有优化的空间,在push的时候并不用把s2中的元素转移到s1中,直接push到s1中即可,pop和peek的时候也不一定要将s1中的元素转移到s2中,只有当s2空了的时候才需要将s1中的元素全部转移到s2。
- 时间复杂度:同上。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class MyQueue {
public:
MyQueue() {
this->s1 = new stack<int>();
this->s2 = new stack<int>();
}
void push(int x) {
this->s1->push(x);
}
int pop() {
int val = this->peek();
this->s2->pop();
return val;
}
int peek() {
if (this->s2->empty()) this->update();
return this->s2->top();
}
void update() {
while (!this->s1->empty()) {
this->s2->push(this->s1->top());
this->s1->pop();
}
}
bool empty() {
return this->s1->empty() && this->s2->empty();
}
private:
stack<int>* s1;
stack<int>* s2;
};
看完讲解的思考
无。
代码实现遇到的问题
无。
225. 用队列实现栈
方法一:双队列
思路
创建两个队列q1和q2,q1用于存储元素,q2只在pop的时候用到。
- push:向q1中push。
- pop:记录下q1中队尾的元素,该元素就是最后要返回的元素,也是需要删除的元素。将q1中除了队尾的其他元素逐个出列并入列q2,q1的队尾元素仅出列不入列,将该元素删除,然后再将q2的元素转移回q1。
- top:返回q1的队尾。
- empty:若q1为空则为true,q1不为空则为false。
- 时间复杂度:pop为 O ( n ) O(n) O(n),其余操作均为 O ( 1 ) O(1) O(1)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class MyStack {
public:
MyStack() {
this->q1 = new queue<int>();
this->q2 = new queue<int>();
}
void push(int x) {
this->q1->push(x);
}
int pop() {
int val = this->q1->back();
int tmp = -1;
while (!this->q1->empty()) {
if (tmp != -1) this->q2->push(tmp);
tmp = this->q1->front();
this->q1->pop();
}
while (!this->q2->empty()) {
tmp = this->q2->front();
this->q2->pop();
this->q1->push(tmp);
}
return val;
}
int top() {
return this->q1->back();
}
bool empty() {
return this->q1->empty() && this->q2->empty();
}
private:
queue<int>* q1;
queue<int>* q2;
};
方法二:单队列
用时:10m35s
思路
方法一只有在pop时才用到第二个队列来临时存储出列的元素,实际上可以不用第二个队列,直接将出列的元素再次入列原先的队列即可,直到最后一个元素出列就不再入列。
- 时间复杂度:同上。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class MyStack {
public:
MyStack() {
this->q = new queue<int>();
}
void push(int x) {
this->q->push(x);
}
int myPop() {
// 自己想的实现方法,搞复杂了
int val = this->q->back();
this->q->push(-1);
int out = 0;
int front = this->q->front();
while (front != -1) {
if (out != 0) this->q->push(out);
out = front;
this->q->pop();
front = this->q->front();
}
this->q->pop();
return val;
}
int pop() {
int size = this->q->size();
int val = 0;
while (--size) {
this->q->push(this->q->front());
this->q->pop();
}
val = this->q->front();
this->q->pop();
return val;
}
int top() {
return this->q->back();
}
bool empty() {
return this->q->empty();
}
private:
queue<int>* q;
};
看完讲解的思考
无。
代码实现遇到的问题
算法的基本思想都能想到,就是代码实现的细节不够简练。
20. 有效的括号
方法一:栈
用时:13m21s
思路
如果字符串的长度是奇数则直接返回false。
遇到左括号就将字符入栈。
遇到右括号首先判断栈是否为空,栈为空则直接返回false,因为没有左括号能与右括号匹配;若不为空,判断栈顶元素是否为右括号对应的左括号,若不匹配则返回false,若匹配则出栈。
字符串全部遍历完后再判断栈是否为空,若不为空则说明有多余的左括号没有右括号匹配,返回false,若为空则返回true。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
bool isValid(string s) {
stack<char> sta;
int size = s.size();
if (size % 2 != 0) return false;
for (int i = 0; i < size; ++i) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{') sta.push(s[i]);
else if (sta.empty() || (s[i] == ')' && sta.top() != '(') || (s[i] == ']' && sta.top() != '[') || (s[i] == '}' && sta.top() != '{')) return false;
else sta.pop();
}
return sta.empty();
}
};
看完讲解的思考
无。
代码实现遇到的问题
一开始没有注意到字符串全部遍历完后,栈中仍有字符的情况,以及出现右括号但是栈为空的情况,导致程序有bug。
1047. 删除字符串中的所有相邻重复项
方法一:栈
用时:7m19s
思路
遍历字符串,与栈顶元素不一致就入栈,一致就将栈顶元素出栈,最后再将栈转化为字符串。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
string removeDuplicates(string s) {
stack<char> sta;
int size = s.size();
string res;
for (int i = 0; i < size; ++i) {
if (!sta.empty() && sta.top() == s[i]) sta.pop();
else sta.push(s[i]);
}
res.resize(sta.size());
for (int i = sta.size() - 1; i >= 0; --i) {
res[i] = sta.top();
sta.pop();
}
return res;
}
};
方法二:栈的思想但用字符串实现
思路
思路与方法一一致,区别在于直接用字符串实现,因为c++的string内置了push_back和pop_back方法,所以可以当作栈来使用。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
string removeDuplicates(string s) {
int size = s.size();
string res;
for (int i = 0; i < size; ++i) {
if (!res.empty() && res.back() == s[i]) res.pop_back();
else res.push_back(s[i]);
}
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
最后的碎碎念
在家白天刷题效率总是很低,到晚上效率才高点,明天要更专注才行啊!偷瞄了一眼明天的几道题目,几道中等和困难,看来明天是场恶战。