Skip to content

Latest commit

 

History

History
602 lines (553 loc) · 18.7 KB

栈与队列.md

File metadata and controls

602 lines (553 loc) · 18.7 KB

栈与队列理论基础

  1. 栈和队列也是SGI STL里面的数据结构,栈后进先出,队列先进先出,栈和队列都不提供迭代器
  2. SGI STL中 栈和队列的底层实现缺省的情况下都是用 deque 双端队列实现的

栈与队列中常见的面试题型

  1. 利用栈和队列的性质解题
  2. 单调栈
  3. 单调队列
  4. 优先队列

栈与队列题型

leetcode.232 用栈实现队列

class MyQueue {
public:
    stack<int> a, b;
    MyQueue() {
    }
    
    void push(int x) {
        a.push(x);
    }
    
    int pop() {
        // 保留一个元素用来返回答案
        while (a.size() > 1) b.push(a.top()), a.pop();
        int cur = a.top();
        a.pop();
        // 恢复现场
        while (b.size()) a.push(b.top()), b.pop();
        return cur;
    }
    
    int peek() {
        while (a.size() > 1) b.push(a.top()), a.pop();
        int cur = a.top();
        while (b.size()) a.push(b.top()), b.pop();
        return cur;
    }
    
    bool empty() {
        return a.empty();
    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */

leetcode.225 用队列实现栈

class MyStack {
public:
    queue<int> a, b;
    MyStack() {
    }
    
    void push(int x) {
        a.push(x);
    }
    
    int pop() {
        while (a.size() > 1) b.push(a.front()), a.pop();
        auto cur = a.front();
        a.pop();
        while (b.size()) a.push(b.front()), b.pop();
        return cur;
    }
    
    int top() {
        while (a.size() > 1) b.push(a.front()), a.pop();
        auto cur = a.front();
        b.push(a.front()), a.pop();
        while (b.size()) a.push(b.front()), b.pop();
        return cur;
    }
    
    bool empty() {
        return a.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();
 */

leetcode.20 有效的括号

  • 链接https://leetcode.cn/problems/valid-parentheses/
  • 解题方法1:先将所有的左括号入栈,注意入栈顺序要跟匹配顺序相同
    如果右括号与当前栈顶左括号匹配,那么弹出栈顶元素,否则返回 false,直到栈为空匹配完成
  • leetcode解题代码
class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for (auto c: s){
            if (c == '(' || c == '[' || c == '{') stk.push(c);
            else if (c == ')'){
                if (stk.empty() || stk.top() != '(')
                    return false;
                stk.pop();
            }
            else if (c == ']'){
                if (stk.empty() || stk.top() != '[')
                    return false;
                stk.pop();
            }
            else if (c == '}'){
                if (stk.empty() || stk.top() != '{')
                    return false;
                stk.pop();
            }
        }
        return stk.empty();
    }
};
  • 解题方法2:先将所有的左括号入栈,注意入栈顺序要跟匹配顺序相同
    匹配右括号,可以查询 ASKII 码发现无论是大中小括号,其左右括号的 ASKII 码相差小于等于 2
  • leetcode解题代码
class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for (auto c: s){
            if (c == '(' || c == '[' || c == '{') stk.push(c);
            else {
                if (stk.empty() || abs(c - stk.top()) > 2) return false;
                stk.pop();
            }
        }
        return stk.empty();
    }
};

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

class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> stk;
        string res;
        for (auto c: s){
            if (stk.size() && stk.top() == c) stk.pop();
            else stk.push(c);
        }

        while (stk.size()){
            res += stk.top();
            stk.pop();
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

leetcode.150 逆波兰表达式求值

  • 链接https://leetcode.cn/problems/evaluate-reverse-polish-notation/
  • 解题方法:遍历所有字符,如果字符为运算符则将当前栈顶的两个元素弹出做相应计算(注意:弹出是先弹出后面的元素,减法和除法需要注意元素计算顺序)
    如果字符为数字,则入栈(注意转换格式),最后返回栈顶元素的值即为答案
  • leetcode解题代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> stk;
        for (auto c: tokens){
            if (c == "+" || c == "-" || c == "*" || c == "/"){
                long b = stk.top(); stk.pop();
                long a = stk.top(); stk.pop();
                if (c == "+") stk.push(a + b);
                else if (c == "-") stk.push(a - b);
                else if (c == "*") stk.push(a * b);
                else if (c == "/") stk.push(a / b);
            }
            else stk.push(stoi(c));
        }
        return stk.top();
    }
};

单调栈

  • 常见模型:找出每个数左边/右边离它最近的比它大/小的数
  • 如果是找左边第一个比它小/大的数,那么从左往右遍历入栈,如果当前数小于等于/大于等于栈顶元素,那么栈顶元素永远不可能作为答案输出,则删除栈顶元素
  • 如果是找右边第一个比它小/大的数,那么从右往左遍历入栈,如果当前数小于等于/大于等于栈顶元素,那么栈顶元素永远不可能作为答案输出,则删除栈顶元素
  • 单调栈模板
for (int i = 0; i < n; i ++){
    while (stk.size() && check(nums[i])) stk.pop();
    ...
    stk.push();
}
for (int i = n - 1; i >= 0; i --){
    while (stk.size() && check(nums[i])) stk.pop();
    ...
    stk.push();
}

单调栈题型

Acwing.830 单调栈

给定一个长度为 $N$ 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 $−1$

输入格式
第一行包含整数 $N$,表示数列长度。

第二行包含 $N$ 个整数,表示整数数列。

输出格式
共一行,包含 $N$ 个整数,其中第 $i$ 个数表示第 $i$ 个数的左边第一个比它小的数,如果不存在则输出 $−1$

数据范围
$1 ≤ N ≤ 10^5$
$1 ≤ 数列中元素 ≤ 10^9$

输入样例

5
3 4 2 7 5

输出样例:

-1 3 -1 2 2
  • 解题代码
#include <iostream>
#include <stack>

using namespace std;

const int N = 1e5 + 10;

int n;
int nums[N];

int main(){
    cin >> n;
    for (int i = 0; i < n; i ++) cin >> nums[i];
    
    stack<int> stk;
    for (int i = 0; i < n; i ++){
        while (stk.size() && nums[i] <= stk.top()) stk.pop();
        if (stk.size()) cout << stk.top() << ' ';
        else cout << -1 << ' ';
        stk.push(nums[i]);
    }
    return 0;
}

leetcode.739 每日温度

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& t) {
        stack<int> stk;
        vector<int> res(t.size());
        for (int i = t.size() - 1; i >= 0; i --){
            while (stk.size() && t[i] >= t[stk.top()]) stk.pop();
            if (stk.size()) res[i] = stk.top() - i;
            stk.push(i);
        }
        return res;
    }
};

leetcode.496 下一个更大元素 I

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        stack<int> stk;
        vector<int> q(nums2.size());
        // 找到 nums2 中每个数的下一个更大元素,存在数组 q 中
        for (int i = nums2.size() - 1; i >= 0; i --){
            while (stk.size() && nums2[i] >= stk.top()) stk.pop();
            if (stk.size()) q[i] = stk.top();
            else q[i] = -1;
            stk.push(nums2[i]);
        }
        // 用哈希表存 nums2 中每个元素的下标
        unordered_map<int, int> hash;
        for (int i = 0; i < nums2.size(); i ++) hash[nums2[i]] = i;

        vector<int> res;
        // 找到 nums1 中元素在 nums2 中对应的下标,将数组 q 中的值取出来即为答案
        for (auto c: nums1){
            res.push_back(q[hash[c]]);
        }
        return res;
    }
};

leetcode.503 下一个更大元素 II

class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        int n = nums.size();
        stack<int> stk;
        vector<int> res(n * 2);
        nums.insert(nums.end(), nums.begin(), nums.end());
        for (int i = n * 2 - 1; i >= 0; i --){
            while (stk.size() && nums[i] >= stk.top()) stk.pop();
            if (stk.size()) res[i] = stk.top();
            else res[i] = -1;
            stk.push(nums[i]);
        }
        res.resize(n);
        return res;
    }
};

单调队列

  • 常见模型:找出滑动窗口中的最大/最小值
  • 如果求滑动窗口的最大值那么即为一个递减序列(队头为最大值),如果当前元素大于等于队尾元素,那么队尾元素永远不可能作为结果输出,删除队尾元素
  • 如果求滑动窗口的最小值那么即为一个递增序列(队头为最小值),如果当前元素小于等于队尾元素,那么队尾元素永远不可能作为结果输出,删除队尾元素
  • 单调队列模板
for (int i = 0; i < n; i ++ )
{
    // 合法性判断(是否超出滑动窗口的大小)
    if (q.size() && check()) q.pop_front();  // 判断队头是否滑出窗口
    // 单调性判断(添加的元素是否符合队列单调性)
    while (q.size() && check()) q.pop_back();
    // 在队尾添加元素
    q.push_back(i);
    // 能够形成滑动窗口了开始维护答案
    if (i >= k - 1) res.push_back();
}

单调队列题型

Acwing.154 滑动窗口

给定一个大小为 $n ≤ 10^6$ 的数组。

有一个大小为 $k$ 的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 $k$ 个数字。

每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7],$k$ 为 $3$

窗口位置 最小值 最大值
[1 3 -1] -3 5 3 6 7 -1 3
1 [3 -1 -3] 5 3 6 7 -3 3
1 3 [-1 -3 5] 3 6 7 -3 5
1 3 -1 [-3 5 3] 6 7 -3 5
1 3 -1 -3 [5 3 6] 7 3 6
1 3 -1 -3 5 [3 6 7] 3 7

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式
输入包含两行。

第一行包含两个整数 $n$$k$,分别代表数组长度和滑动窗口的长度。

第二行有 $n$ 个整数,代表数组的具体数值。

同行数据之间用空格隔开。

输出格式
输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。

第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例:

8 3  
1 3 -1 -3 5 3 6 7

输出样例:

-1 -3 -3 -3 3 3
3 3 5 5 6 7
  • 解题代码
#include <iostream>
#include <deque>

using namespace std;

const int N = 1e6 + 10;

int n, k;
int nums[N];

int main(){
    cin >> n >> k;
    for (int i = 0; i < n; i ++) cin >> nums[i];
    
    deque<int> q1;
    for (int i = 0; i < n; i ++){
        if (q1.size() && i - q1.front() + 1 > k) q1.pop_front();
        while (q1.size() && nums[i] <= nums[q1.back()]) q1.pop_back();
        q1.push_back(i);
        if (i >= k - 1) cout << nums[q1.front()] << ' ';
    }
    
    puts("");
    deque<int> q2;
    for (int i = 0; i < n; i ++){
        if (q2.size() && i - q2.front() + 1 > k) q2.pop_front();
        while (q2.size() && nums[i] >= nums[q2.back()]) q2.pop_back();
        q2.push_back(i);
        if (i >= k - 1) cout << nums[q2.front()] << ' ';
    }
}

leetcode.239 滑动窗口最大值

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> q;
        vector<int> res;
        for (int i = 0; i < nums.size(); i ++){
            while (q.size() && i - q.front() + 1 > k) q.pop_front();
            while (q.size() && nums[i] >= nums[q.back()]) q.pop_back();
            q.push_back(i);
            if (i >= k - 1) res.push_back(nums[q.front()]);
        }
        return res;
    }
};

优先队列

  • 优先队列是一种会按照默认或自定义的优先级进行自动排序的队列,其特点是优先级高的元素排在队首,低的排在队尾,可以理解为维护一个堆
  • 头文件#include <queue>中提供了两种可直接引用的优先规则(排序规则):greater、less;其中,less 是默认优先规则,小的元素排在堆底,大的元素在堆顶也叫大根堆;greater 表示小根堆,大的元素排在堆底,小的元素在堆顶

优先队列经典题型

leetcode.215 数组中的第 K 个最大元素

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // 优先队列,建小根堆即堆顶元素为最小值,这样更新的时候可以删除堆顶元素
        priority_queue<int, vector<int>, greater<int>> heap;
        // 直接入堆,如果大于 k 则删除元素(删除的都是最小值)
        for (auto c: nums){ 
            heap.push(c);
            if (heap.size() > k){
                heap.pop();
            }
        }
        // 输出
        return heap.top();
    }
};

leetcode.347 前 K 个高频元素

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 用哈希表记录每个数字出现的次数
        unordered_map<int, int> hash;
        for (auto c: nums) hash[c] ++;
        // 重载优先队列排序,pair<数字,出现的次数>,以数字出现的次数排序
        struct mycompare{
            bool operator () (const pair<int, int> &a, const pair<int, int> &b){
            return a.second > b.second;
            }
        };
        // 定义优先队列
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycompare> heap;
        // 将哈希表入队,如果元素个数超过 k 则删除堆顶元素(最小值)
        for (auto x: hash){
            heap.push(x);
            if (heap.size() > k){
                heap.pop();
            }
        }
        // 输出结果,输出的是 pair 中的数字
        vector<int> res;
        while (heap.size()){
            res.push_back(heap.top().first);
            heap.pop();
        }
        return res;
    }
};

leetcode.23 合并 K 个升序链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    // 重载比较函数,将小的元素放在堆顶,大的元素放在堆底
    struct Cmp{
        bool operator() (ListNode* a, ListNode* b){
            return a->val > b->val;
        }
    };

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*, vector<ListNode*>, Cmp> heap;
        auto dummy = new ListNode(-1);
        auto cur = dummy;
        // 将所有链表数组入堆,会自动排序将头元素进行排序
        for (auto c: lists) 
            if (c) heap.push(c);

        while (heap.size()){
            auto t = heap.top();
            heap.pop();

            cur = cur->next = t;
            if (t->next) heap.push(t->next);
        }
        return dummy->next;
    }
};