代码随想录Day14-字符串&栈与队列:力扣第459、232、225、20、1047题

459. 重复的子字符串

题目链接
代码随想录文章讲解链接

方法一:暴力解法

用时:12m55s

思路

遍历每一个前缀子串,判断字符串能否以该子串重复构成。
注意:

  1. 如果子串的长度大于s长度的一半,那s不可能由该子串重复构成。
  2. 子串长度若不可以整除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

思路
  1. 设字符串的长度为 L 1 L_1 L1,字符串的最长相等前后缀的长度为 L 2 L_2 L2,若 L 1 L_1 L1可以被 ( L 1 − L 2 ) (L_1-L_2) (L1L2)整除,则该字符串可以由重复的子串构成。
  2. 理由:如图所示,字符串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]重复构成。
  3. 总结:最长相等后缀不包含的子串,当满足第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

思路

创建两个栈s1s2

  • push操作:将s2中的所有元素逐个弹出并压入s1中,然后再将新的元素压入s1
  • poppeek操作:将s1中的所有元素逐个弹出并压入s2中,然后再弹出并返回s2栈顶元素(pop)或者返回栈顶元素(peek)。
  • empty操作:只有当s1s2都为空时才返回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;
    }
};

看完讲解的思考

无。

代码实现遇到的问题

无。


最后的碎碎念

在家白天刷题效率总是很低,到晚上效率才高点,明天要更专注才行啊!偷瞄了一眼明天的几道题目,几道中等和困难,看来明天是场恶战。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值