算法打卡第10天
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++;}// 更新nextnext[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)