栈与队列

0. 用到的基础功能库 tools.h

代码如下:

点击查看代码
#include<vector>
#include<queue>
#include<stack>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;

struct ListNode
{
   int val;
    ListNode* next;
    ListNode(int val) : val(val), next(nullptr) {}
};

template<typename T>
void showArr(const vector<T>& nums)
{
    cout << '[';
    for (int i = 0; i < nums.size(); ++i)
    {
        cout << nums[i];
        if (i != nums.size() - 1)
            cout << ',';
    }
    cout << ']' << endl;
}

void showList(ListNode* head)
{   
    ListNode* cur = head;
    cout << "[";
    while (cur)
    {
        cout << cur->val << ", ";
        cur = cur->next;
    }
    cout << "]" << endl;
}

template<typename T>
void showArr2D(const vector<vector<T>>& nums)
{
    cout << '[';
    for (int i = 0; i < nums.size(); ++i)
    {
        cout << '[';
        for (int j = 0; j < nums[i].size(); ++j)
        {
            cout << nums[i][j];
            if (j != nums[i].size() - 1)
                cout << ',';
        }
        cout << ']';
        if (i != nums.size() - 1)
                cout << ',';
    }
    cout << ']' << endl;
}

1. 用栈实现队列

使用栈实现队列的下列操作:
push(x) -- 将一个元素放入队列的尾部。
pop() -- 从队列首部移除元素。
peek() -- 返回队列首部的元素。
empty() -- 返回队列是否为空。

示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek();  // 返回 1
queue.pop();   // 返回 1
queue.empty(); // 返回 false

说明:
你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。

代码如下:

点击查看代码
#include"../tools.h"

class MyQueue
{
public:
    MyQueue() {}
    void push(int val);
    int peek();
    int pop();
    bool empty();
private:
    stack<int> InStack;
    stack<int> OutStack;
    void Stack2Stack(stack<int>& Out, stack<int>& In);
};

void MyQueue::Stack2Stack(stack<int>& Out, stack<int>& In)
{
    while (!Out.empty())
    {
        int tmp = Out.top();
        Out.pop();
        In.push(tmp);
    }
    return;
}

void MyQueue::push(int val)
{
    InStack.push(val);
    return;
}

int MyQueue::peek()
{
    if (this->InStack.empty()) return -1;
    Stack2Stack(this->InStack, this->OutStack);
    int peek = this->OutStack.top();
    Stack2Stack(this->OutStack, this->InStack);
    return peek; 
}

int MyQueue::pop()
{
    if (this->InStack.empty()) return -1;
    Stack2Stack(this->InStack, this->OutStack);
    int peek = this->OutStack.top();
    this->OutStack.pop();
    Stack2Stack(this->OutStack, this->InStack);
    return peek;
}

bool MyQueue::empty()
{
    return InStack.empty();
}

int main()
{
    MyQueue* queue = new MyQueue();
    queue->push(1);
    queue->push(2);
    cout << queue->peek() << endl;  // 返回 1
    cout << queue->pop() << endl;   // 返回 1
    string res = queue->empty() ? "true" : "false";
    cout << res << endl; // 返回 false
    return 0;
}

2. 用队列实现栈

使用队列实现栈的下列操作:

  • push(x) -- 元素 x 入栈
  • pop() -- 移除栈顶元素
  • top() -- 获取栈顶元素
  • empty() -- 返回栈是否为空

注意:

  • 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
  • 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
  • 你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

代码如下:

点击查看代码
#include"../tools.h"

class MyStack
{
public:
    MyStack() {}
    void push(int val);
    int top();
    int pop();
    bool empty();
private:
    queue<int> In;
};

void MyStack::push(int val)
{
    In.push(val);
    return;
}

int MyStack::top()
{
    if (this->In.empty()) return -1;
    int top = this->In.back();
    return top; 
}

int MyStack::pop()
{
    if (this->In.empty()) return -1;
    int size = In.size();
    --size; // 除最后一个元素,其余重新进入队列
    while (size--)
    {
        In.push(In.front());
        In.pop();
    }
    int pop = In.front();
    In.pop();
    return pop;
}

bool MyStack::empty()
{
    return In.empty();
}

int main()
{
    MyStack* stack = new MyStack();
    stack->push(1);
    stack->push(2);
    cout << stack->top() << endl;  // 返回 1
    cout << stack->pop() << endl;   // 返回 1
    string res = stack->empty() ? "true" : "false";
    cout << res << endl; // 返回 false
    return 0;
}

3. 有效的括号

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 1:
输入: "()"
输出: true

示例 2:
输入: "()[]{}"
输出: true

示例 3:
输入: "(]"
输出: false

示例 4:
输入: "([)]"
输出: false

示例 5:
输入: "{[]}"
输出: true

代码如下:

点击查看代码
#include"../tools.h"

bool BracketsMatch(string s)
{
    if (s.size() == 0) return true;
    if (s.size() % 2 != 0) return false;
    stack<char> In;
    for (int i = 0; i < s.size(); ++i)
    {
        bool condMatch1 = s[i] == '}' && !In.empty() && In.top() == '{';
        bool condMatch2 = s[i] == ']' && !In.empty() && In.top() == '[';
        bool condMatch3 = s[i] == ')' && !In.empty() && In.top() == '(';

        if (condMatch1 || condMatch2 || condMatch3)
        {
            In.pop();
        }
        else
        {
            In.push(s[i]);
        }
    }
    return In.empty();
}

int main()
{
    string a = "()[]{}";
    string res = BracketsMatch(a) ? "true" : "false";
    cout << res << endl;
    return 0;
}

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

给出由小写字母组成的字符串 S,重复项删除操作会选择 2 个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

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

提示:
1 <= S.length <= 20000
S 仅由小写英文字母组成

代码如下:

点击查看代码
void RemoveRepStr(string& s)
{
    stack<char> In;
    for (int i = 0; i < s.size(); ++i)
    {
        if (In.empty())
        {
            In.push(s[i]);
        }
        else 
        {
            if (s[i] == In.top()) In.pop();
            else In.push(s[i]);
        }
    }

    s.resize(In.size());
    int i = In.size() - 1;
    while (!In.empty())
    {   
        s[i--] = In.top();
        In.pop();
    }
}

int main()
{
    string s;
    cin >> s;
    RemoveRepStr(s);
    cout << s << endl;
    return 0;
}

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

给出由小写字母组成的字符串 S,重复项删除操作会选择 num 个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

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

image

代码如下:

点击查看代码
#include"../tools.h"
#include<unordered_map>

void RemoveRepStr(vector<string>& s, int num)
{
    // 计分
    unordered_map<string, int> CountMap;
    for (int i = 0; i < s.size(); ++i)
    {
        if (i > 0 && s[i] == s[i-1])
        {
            ++CountMap[s[i]];
            if (CountMap[s[i]] == num - 1)
            {
                CountMap.erase(s[i]);
                s.erase(s.begin() + i - num + 1, s.begin() + i + 1); // 清除
                i = i - num; // 回档,准备下轮遍历
            }
        }
    }
}

int main()
{
    int n;
    cin >> n;
    vector<string> s(n);
    for (int i = 0; i < n; ++i)
    {
        cin >> s[i];
    }
    int num;
    cin >> num;
    RemoveRepStr(s, num);
    showArr(s);
    return 0;
}

6. 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。
有效的运算符包括 + , - , * , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:
整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1:
输入: ["2", "1", "+", "3", " * "]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:
输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:
输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"]
输出: 22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5       
= ((10 * (6 / (12 * -11))) + 17) + 5       
= ((10 * (6 / -132)) + 17) + 5     
= ((10 * 0) + 17) + 5     
= (0 + 17) + 5    
= 17 + 5    
= 22    

逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。

代码如下:

点击查看代码
#include"../tools.h"
int StrToInt(string str)
{
    int num = 0;
    bool neg = false;
    for (int i = str.size() - 1; i >= 0; --i)
    {
        if (str[i] == '-')
        {
            neg = true;
            continue;
        }
        if (i == str.size() - 1)
        {
            num += str[i] - '0';
            continue;
        }
        num += (str[i] - '0') * pow(10, (str.size() - 1 - i));
    }

    return neg ? -num : num;
}

int RPNCalu(const vector<string>& formula)
{
    stack<int> In;
    for (auto& str : formula)
    {
        if (str == "+" || str == "*" || str == "/" || str == "-")
        {
            int num1 = In.top();
            In.pop();
            int num2 = In.top();
            In.pop();
            if (str == "+")
                In.push((num2 + num1));
            else if (str == "/")
                In.push((num2 / num1));
            else if (str == "*")
                In.push((num2 * num1));
            else
                In.push((num2 - num1));
        }
        else
            // In.push(stoi(str)); // 也可以直接使用 string 库里的 stoi
            In.push(StrToInt(str));
            
    }
    return In.top();
}

int main()
{
    vector<string> formula = {"-78","-33","196","+","-19","-","115","+","-","-99","/",
    "-18","8","*","-86","-","-","16","/","26","-14","-","-","47","-","101",
    "-","163","*","143","-","0","-","171","+","120","*","-60","+","156","/",
    "173","/","-24","11","+","21","/","*","44","*","180","70","-40","-","*",
    "86","132","-84","+","*","-","38","/","/","21","28","/","+","83","/","-31",
    "156","-","+","28","/","95","-","120","+","8","*","90","-","-94","*","-73","/",
    "-62","/","93","*","196","-","-59","+","187","-","143","/","-79","-89","+","-"};
    cout << RPNCalu(formula);
    return 0;
}

7. 滑动窗口最大值

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

返回滑动窗口中的最大值。
在线性时间复杂度内解决此题。

示例:
输入:
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

代码如下:

点击查看代码
#include"../tools.h"
#include<deque>
// 单调队列
class MyQueue{
public:
    void pop(int val)
    {
        if (!que.empty() && val == que.front())
        {
            que.pop_front();
        }
    }

    void push(int val)
    {
        while (!que.empty() && val > que.back())
        {
            que.pop_back();
        }
        que.push_back(val);
    }

    int front()
    {
        return que.front();
    }
private:
    deque<int> que;
};

vector<int> SlideWindowMax(vector<int> nums, int k)
{
    vector<int> ans;
    MyQueue que;
    for (int i = 0; i < k; ++i)
        // 一开始就是有序的,之后比较只需挨个弹出比较就可以确定顺序
        que.push(nums[i]);
    ans.emplace_back(que.front());
    for (int i = k; i < nums.size(); ++i)
    {
        // 最大值弹出时,其前面的值早已弹出,剩余的值逐次比较选新大王
        que.pop(nums[i - k]);
        que.push(nums[i]);
        ans.emplace_back(que.front());
    }
    return ans;
}

int main()
{
    vector<int> nums = {1,3,1,2,0,5};
    int k = 3;
    showArr(SlideWindowMax(nums, k));
    return 0;
}

8. 前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

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

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

提示:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(nlog n) , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。

代码如下:

点击查看代码
#include"../tools.h"
#include<unordered_map>
class myComp
{
public:
    bool operator()(const pair<int, int>& left, const pair<int, int>& right)
    {
        // 小根堆
        return left.second > right.second;
    }
};

vector<int> getKNum(const vector<int>& nums, int k)
{
    unordered_map<int, int> map;
    // 统计频率
    for (auto n : nums)
        ++map[n];
    // 频率排序
    priority_queue<pair<int, int>, vector<pair<int, int>>, myComp> priQue;
    for (auto m : map)
    {
        priQue.push(m);
        if (priQue.size() > k)
            // 只保留前k个大的
            priQue.pop();
    }
    vector<int> ans(k);
    // 倒序接收
    for (int i = k - 1; i >= 0; --i)
    {
        ans[i] = priQue.top().first;
        priQue.pop(); 
    }
    return ans;
}

int main()
{
    vector<int> nums = {1,1,1,2,2,3};
    int k = 2;
    showArr(getKNum(nums, k));
    return 0;
}
posted @ 2024-04-19 08:29  bok_tech  阅读(5)  评论(0)    收藏  举报