33 重复的子字符串
(力扣459题)
示例 1:
输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。
示例 2:
输入: s = "aba"
输出: false
示例 3:
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。
示例 2:
输入: s = "aba"
输出: false
示例 3:
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
提示:
1 <= s.length <= 104
s
由小写英文字母组成
解题思路
题目要求判断一个字符串是否可以通过重复其子串来构造。核心思路是利用 KMP 算法中的前缀表(next
数组)来判断。
- 计算前缀表:
- 使用 KMP 算法的
getNext
函数计算字符串s
的前缀表。前缀表的每个位置next[i]
表示字符串s
的前i+1
个字符中,最长相同前后缀的长度。 - 通过遍历字符串,利用已计算的前缀表值逐步构建完整的前缀表。
- 使用 KMP 算法的
- 判断重复子串:
- 如果字符串长度
len
能被(len - next[len - 1])
整除,说明字符串可以由长度为(len - next[len - 1])
的子串重复构成。 next[len - 1]
表示整个字符串的最长相同前后缀长度,如果该长度不为零且满足上述条件,则返回true
,否则返回false
。
- 如果字符串长度
- 特殊情况处理:
- 如果字符串为空,直接返回
false
。
- 如果字符串为空,直接返回
代码
#include <iostream>
using namespace std;
class Solution
{
public:
// KMP算法
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
next[i] = j;
}
}
bool repeatedSubstringPattern(string s)
{
if(s.size() == 0)
{
return 0;
}
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;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(n)
34.用栈实现队列
(力扣232题)
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、peek
和empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用
pop
或者peek
操作)
进阶:
- 你能否实现每个操作均摊时间复杂度为
O(1)
的队列?换句话说,执行n
个操作的总时间复杂度为O(n)
,即使其中一个操作可能花费较长时间。
解题思路
使用栈来模拟队列的行为,如果仅仅用一个栈,是一定不行的,需要两个栈一个输入栈,一个输出栈,这里要注意输入栈和输出栈的关系
push
操作:直接将元素压入stIN
栈。pop
操作:如果stOut
栈为空,将stIN
栈的所有元素依次弹出并压入stOut
栈。这样,stOut
栈的栈顶元素就是队列的首元素。然后弹出并返回该元素。peek
操作:与pop
类似,但需要将弹出的元素重新压入stOut
栈,以保持队列状态不变。empty
操作:判断两个栈是否都为空,如果都为空,则队列为空。
class MyQueue
{
public:
stack<int> stIN;
stack<int> stOut;
MyQueue()
{
}
// 元素压入stIn栈
void push(int x)
{
stIN.push(x);
}
// 队列前面移除元素并返回该元素
int pop()
{
// 如果输出栈为空
if (stOut.empty())
{
// 将输入栈中的所有元素转移到输出栈
while (!stIN.empty())
{
// 将输入栈的栈顶元素压入输出栈
stOut.push(stIN.top());
// 弹出输入栈的栈顶元素
stIN.pop();
}
}
// 获取输出栈的栈顶元素(即队列的首元素)
int result = stOut.top();
// 弹出输出栈的栈顶元素
stOut.pop();
// 返回队列的首元素
return result;
}
int peek()
{
// 获取队列前面的元素,但不移除他
int res = this->pop();
// 因为pop函数弹出了元素res,所以再添加回输出栈
stOut.push(res);
// 返回队列的首元素
return res;
}
// 判断队列是否为空
bool empty()
{
// 当输入栈和输出栈都为空时,队列为空
return stIN.empty() && stOut.empty();
}
};
- 时间复杂度: 都为O(1)。pop和peek看起来像O(n),实际上一个循环n会被使用n次,最后还是O(1)。
- 空间复杂度: O(n)
35.用队列实现栈
(力扣255题)
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
注意:
- 你只能使用队列的标准操作 —— 也就是
push to back
、peek/pop from front
、size
和is empty
这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
-
解题思路
push
操作:- 直接将元素压入队列的末尾。这个操作与队列的
push
方法一致。
- 直接将元素压入队列的末尾。这个操作与队列的
pop
和top
操作:- 为了模拟栈的
pop
和top
操作,需要将队列的最后一个元素(即栈顶元素)移到队列的前面。 - 通过将队列的前
size-1
个元素依次弹出并重新添加到队列尾部,使得最后一个元素移到队列的前面。 - 对于
pop
操作,弹出并返回队列的队首元素。 - 对于
top
操作,获取队首元素后,再将其重新加回到队列尾部,以保持队列的原始状态。
- 为了模拟栈的
empty
操作:- 直接检查队列是否为空。如果队列为空,返回
true
;否则返回false
。
- 直接检查队列是否为空。如果队列为空,返回
push
操作:- 直接将元素压入队列的末尾。这个操作与队列的
push
方法一致。
- 直接将元素压入队列的末尾。这个操作与队列的
pop
和top
操作:- 为了模拟栈的
pop
和top
操作,需要将队列的最后一个元素(即栈顶元素)移到队列的前面。 - 通过将队列的前
size-1
个元素依次弹出并重新添加到队列尾部,使得最后一个元素移到队列的前面。 - 对于
pop
操作,弹出并返回队列的队首元素。 - 对于
top
操作,获取队首元素后,再将其重新加回到队列尾部,以保持队列的原始状态。
- 为了模拟栈的
empty
操作:- 直接检查队列是否为空。如果队列为空,返回
true
;否则返回false
。
- 直接检查队列是否为空。如果队列为空,返回
代码
#include <iostream>
#include <queue>
using namespace std;
/*请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。*/
class MyStack
{
public:
queue<int> que;
MyStack()
{
}
// 将元素 x 推到队列的末尾
void push(int x)
{
que.push(x);
}
// 移除并返回栈顶元素
int pop()
{
int size = que.size();
size--;
// 将队列头部的元素(除了最后一个元素外) 弹出再重新添加到队列尾部
while (size--)
{
que.push(que.front());
que.pop();
}
// 此时弹出的元素顺序就是栈的顺序了
int result = que.front();
que.pop();
return result;
}
// 返回栈顶元素。
int top()
{
int size = que.size();
size--;
// 将队列头部的元素(除了最后一个元素外) 弹出再重新添加到队列尾部
while(size--)
{
que.push(que.front());
que.pop();
}
// 此时弹出的元素顺序就是栈的顺序了
int result = que.front();
//将获取完的元素也重新添加到队列尾部,保证数据结构没有变化
que.push(que.front());
que.pop();
return result;
}
//如果栈是空的,返回 true ;否则,返回 false
bool empty()
{
return que.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
- 时间复杂度: pop为O(n),top为O(n),其他为O(1)
- 空间复杂度: O(n)