【算法】栈与队列 汇总

63 阅读3分钟

像栈实现队列,队列实现栈这种,蛮简单的,写一下吧还是

栈实现队列

class MyQueue {
public:
    MyQueue() {}
    
    void push(int x) {
        stk1.push(x);
    }
    
    int pop() {
        while (!stk1.empty()) {
            stk2.push(stk1.top());
            stk1.pop(); // 弹出 不返回
        }
        int out = stk2.top(); // 返回
        stk2.pop(); // 弹出
        while (!stk2.empty()) {
            stk1.push(stk2.top());
            stk2.pop(); // 弹出
        }
        return out;
    }
    
    int peek() {
        while (!stk1.empty()) {
            stk2.push(stk1.top());
            stk1.pop(); // 弹出 不返回
        }
        int out = stk2.top(); // 返回
        while (!stk2.empty()) {
            stk1.push(stk2.top());
            stk2.pop(); // 弹出
        }
        return out;
    }
    
    bool empty() {
        if (stk1.empty()) return true;
        else return false;
    }
private:
    stack<int> stk1;
    stack<int> stk2;
};

队列实现栈

class MyStack {
public:
    MyStack() { // 为什么不能在构造函数里面定义队列啊,我觉得吧,是这里面只能初始化,不能定义}
    
    void push(int x) {
        que_1.push(x);
    }
    
    int pop() {
        int out1 = this->top();
        for (int n = que_1.size(); n > 1; n--) {
            que_2.push(que_1.front());
            que_1.pop(); 
        }
        que_1.pop();
        while (!que_2.empty()) {
            que_1.push(que_2.front());
            que_2.pop(); 
        }
        return out1;
    }
    
    int top() {
        return que_1.back();
    }
    
    bool empty() {
        if (que_1.empty()) return true;
        else return false;
    }
private:
    queue<int> que_1, que_2;
};

20. 有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 5:

  • 输入: "{[]}"
  • 输出: true

首先,括号匹配是使用栈解决的经典问题,好神奇,栈这么有用啊,一开始没思路,看了一下题解,一个好的数据结构果然可以提高效率

写的时候有个错误 错误1:if (s[i] == ')' && stks.top() == '(' && !stks.empty())

显示: Segmentation fault(段错误),这是对内存操作不当造成的

错误原因:在栈为空的时候判断if(stks.top() == '(')

修改:&&是短路求值,当左侧无法确定结果的时候才计算右侧的值, 所以说先判断栈是否为空,若为空就不进行第三个判断,因此调换if中后两个判断的顺序

例子:

stack<char> stks;
if (stks.top() == '(') s.push_back('a'); // 在栈为空的时候就会报这样的错误

解题过程

class Solution {
public:
    bool isValid(string s) { 

        stack<char> stks;
        if (s.size() % 2) return false; // 基数个直接返回
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ')'  && !stks.empty() && stks.top() == '(') { // "){"会报错,为什么,
                stks.pop();
                continue;
            }
            else if (s[i] == ']' && !stks.empty()&& stks.top() == '[') {
                stks.pop();
                continue;
            } 
            else if (s[i] == '}' && !stks.empty()&& stks.top() == '{') {
                stks.pop();
                continue;
            }
            stks.push(s[i]);
        }
        if (stks.empty()) return true;
        return false;         
    }
};

1047. 删除字符串中的所有相邻重复项

给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

输入: "abbaca"
输出: "ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"

是一维消消乐,但是假一维消消乐,因为只能是两两相同配对删除,如果出现奇数个就只删前偶数个,"aaa" 要输出"a",艹,我以为连着就要删呢,原来是偶数个删除,"bbba"要输出"ba"

用数据结构来解决,也就是用栈来解决,把元素一个一个放到栈里,栈top和i指针指向的元素对比,若相同,i++,且栈pop,之后继续元素入栈

跟前面的“【算法】哈希表 汇总”用哈希表数据结构的来解决问题一样,不过注意只删偶数个

class Solution { // 原来是假消消乐
public:
    string removeDuplicates(string s) { 
       
        stack<char> st1, st2;
        
        for (int i = 0; i < s.size(); ) {
            if (!st1.empty() && st1.top() == s[i]) { // 发现潜在的一连串相同了
//              while (i < s.size()  && st1.top() == s[i]) { // 加上这个就是真实消消乐
                    i++;
//              }   
                st1.pop();
                continue;
            }
            st1.push(s[i]);
            i++;
        }
        while (!st1.empty()) {
            st2.push(st1.top());
            st1.pop();
        }
        string sout;
        while (!st2.empty()) {
            sout.push_back(st2.top());
            st2.pop();
        }
        return sout;
    }
};

239. 滑动窗口最大值

做的第一个困难题

给你一个整数数组 nums,有一个大小为 k **的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

  示例 1:

输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7      5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

图片思路:

image.png

新设计一个单调队列:单调递减或单调递增的队列

其实就是步骤多一点,不过要用到优先级队列的小顶堆;大顶堆(优先级队列)来存放这个窗口里的k个数字,这样就可以知道最大的最大值是多少了, 但是问题是这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值, 这样就造成大顶堆维护的不是滑动窗口里面的数值了。所以不能用大顶堆,要用小顶堆

常用的queue在没有指定容器的情况下,deque就是默认底层容器

class Solution { // 真不愧是hard啊
public:

    class mDeqQue {
    public:
        // mDeqQue(); // 可以不用写构造函数

        // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
        // 同时pop之前判断队列当前是否为空。
        void pop(int val) {
            if (!deq.empty() && deq.front() == val) deq.pop_front();
        }

        // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
        int front() {
            return deq.front();
        }

        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        void push(int val) {
            while(!deq.empty() && val > deq.back()) {
                deq.pop_back();
            }
            deq.push_back(val);
        }
    private:
        deque<int> deq;
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        mDeqQue nb_deqque;
        vector<int> outmx;
        // 先放前k个,第一个窗口
        for (int i = 0; i < k; i++) {
            nb_deqque.push(nums[i]);
        }
        outmx.push_back(nb_deqque.front());
        // 之后一个一个放
        for (int i = k; i < nums.size(); i++) {
            // 错误1:滑动窗口移除最前面元素
            // 防止元素重复 如 i=3 应该对比的是nums[0] ,而不是nums[i - 1]
            nb_deqque.pop(nums[i - k]); 

            nb_deqque.push(nums[i]);
            outmx.push_back(nb_deqque.front());
        }
        return outmx;    
    }
};

# 347.前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

真是要边用边学,通过这道题我熟悉了一下谓词,还有优先级队列,也就是大顶堆 小顶堆,还有pair,在用的过程中学,这种感觉真爽,这样学的好快

class Solution { 
// 二元谓词
public:
    class mycompar {
    public:
        // bool operator()(pair<int, int>right, pair<int, int>left) { // 这个也行 但是下面的更好
        bool operator()(const pair<int, int>& right, const pair<int, int>& left) {
            return right.second > left.second; // 小顶堆先弹出的是最小的
        }
    };
public:
    bool mycompar2(const pair<int, int>& right, const pair<int, int>& left) {
        return right.second > left.second; 
    } //为什么不可以是这种谓词啊

    vector<int> topKFrequent(vector<int>& nums, int k) { //哈希表?
        // 出现频率前 k 高
        // 不是频率大于k,那unordered_map这个数据结构就不合数了

        // 大顶堆 小顶堆,用小顶堆
        unordered_map<int, int> numap; // 无序map
        vector<int> outun;
        priority_queue<pair<int, int>, vector<pair<int,int>>, mycompar>  pr_que; // 优先级队列

         // 要统计元素出现频率
        for (int i = 0; i < nums.size(); i++) {
            numap[nums[i]]++;
        }

        // 用固定大小为k的小顶堆,扫描所有频率的数值
        for (unordered_map<int, int>::iterator it = numap.begin(); it != numap.end(); it++) {
            pr_que.push(*it);
            if (pr_que.size() > k) pr_que.pop(); // 小顶堆先弹出的是最小的
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,
        // 所以倒序来输出到数组for (int i = k - 1; i >= 0; i--) 但下面我这样也行,因为可以按任意顺序 返回答案
        for (int i = 0; i < k; i++) {
            outun.push_back(pr_que.top().first); // 错误1:不能用pr_que.front(),因为优先级队列压根就没有这个函数
            pr_que.pop();
        }
        return outun;
    }
};