算法打卡第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 数组)来判断。

  1. 计算前缀表
    • 使用 KMP 算法的 getNext 函数计算字符串 s 的前缀表。前缀表的每个位置 next[i] 表示字符串 s 的前 i+1 个字符中,最长相同前后缀的长度。
    • 通过遍历字符串,利用已计算的前缀表值逐步构建完整的前缀表。
  2. 判断重复子串
    • 如果字符串长度 len 能被 (len - next[len - 1]) 整除,说明字符串可以由长度为 (len - next[len - 1]) 的子串重复构成。
    • next[len - 1] 表示整个字符串的最长相同前后缀长度,如果该长度不为零且满足上述条件,则返回 true,否则返回 false
  3. 特殊情况处理
    • 如果字符串为空,直接返回 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题)

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 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
  • 最多调用 100pushpoppeekempty
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)

进阶:

  • 你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。

解题思路

使用栈来模拟队列的行为,如果仅仅用一个栈,是一定不行的,需要两个栈一个输入栈,一个输出栈,这里要注意输入栈和输出栈的关系

  1. push 操作:直接将元素压入 stIN 栈。
  2. pop 操作:如果 stOut 栈为空,将 stIN 栈的所有元素依次弹出并压入 stOut 栈。这样,stOut 栈的栈顶元素就是队列的首元素。然后弹出并返回该元素。
  3. peek 操作:与 pop 类似,但需要将弹出的元素重新压入 stOut 栈,以保持队列状态不变。
  4. 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)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

注意:

  • 你只能使用队列的标准操作 —— 也就是 push to backpeek/pop from frontsizeis 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
  • 解题思路

    1. push 操作
      • 直接将元素压入队列的末尾。这个操作与队列的 push 方法一致。
    2. poptop 操作
      • 为了模拟栈的 poptop 操作,需要将队列的最后一个元素(即栈顶元素)移到队列的前面。
      • 通过将队列的前 size-1 个元素依次弹出并重新添加到队列尾部,使得最后一个元素移到队列的前面。
      • 对于 pop 操作,弹出并返回队列的队首元素。
      • 对于 top 操作,获取队首元素后,再将其重新加回到队列尾部,以保持队列的原始状态。
    3. empty 操作
      • 直接检查队列是否为空。如果队列为空,返回 true;否则返回 false
  1. push 操作
    • 直接将元素压入队列的末尾。这个操作与队列的 push 方法一致。
  2. poptop 操作
    • 为了模拟栈的 poptop 操作,需要将队列的最后一个元素(即栈顶元素)移到队列的前面。
    • 通过将队列的前 size-1 个元素依次弹出并重新添加到队列尾部,使得最后一个元素移到队列的前面。
    • 对于 pop 操作,弹出并返回队列的队首元素。
    • 对于 top 操作,获取队首元素后,再将其重新加回到队列尾部,以保持队列的原始状态。
  3. 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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值