代码随想录刷题笔记6——栈与队列

栈与队列理论基础

先进后出,而队列则是先进先出
代码随想录的栈与队列简易示意图

C

对于直接用C语言来说,两者都是通过malloc一个一维数组直接手动模拟,区别是stack可以直接用一个stkTop来控制入栈出栈;而queue则需要head和tail两个值的变化实现入队出队

C++

对于C++而言,就需要搞清楚用的是哪一个STL(C++标准库),通过对代码随想录的解读,现在用的比较多的应该是SGI STL。

栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。

栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)

所以STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)

常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的底层结构

deque是一个双向队列,只要封住一端,只开通另一端就可以实现栈的逻辑了。

队列中先进先出的数据结构,同样不允许有遍历行为,不提供迭代器, SGI STL中队列一样是以deque为缺省情况下的底部结构

所以STL 队列也不被归类为容器,而被归类为container adapter( 容器适配器)

也正因如此,也可以用诸如vector或者list来进行初始化。

用栈实现队列

例题232(简单)用栈实现队列

注意要点:

  1. 通过两个栈,一个用来压入元素,另一个用来在出队时作为缓冲,来将最后一个出栈元素作为第一个出队元素

下面贴出代码:

CPP版本

class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;
    MyQueue() {

    }
    
    void push(int x) {
        stIn.push(x);
    }
    
    int pop() {
        //stOut空才从stIn导入数据
        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();  //通过this直接调用这个类中的实现函数
        stOut.push(res);  //在pop中弹出,要再次压入栈
        return res;
    }
    
    bool empty() {
        return stIn.empty() && stOut.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();
 */

C版本

typedef struct {
    int stackInTop, stackOutTop;
    int stackIn[101], stackOut[101];
} MyQueue;


MyQueue* myQueueCreate() {
    MyQueue* queue = (MyQueue* )malloc(sizeof(MyQueue));
    queue->stackInTop = 0;
    queue->stackOutTop = 0;
    return queue;
}

void myQueuePush(MyQueue* obj, int x) {
    obj->stackIn[(obj->stackInTop)++] = x;
}

int myQueuePop(MyQueue* obj) {
    //优化:复制栈顶指针,减少对内存的访问次数
    int stackInTop = obj->stackInTop;
    int stackOutTop = obj->stackOutTop;
    //若输出栈为空
    if(!stackOutTop) 
    {
        //将第一个栈中元素复制到第二个栈中
        while(stackInTop) 
        {
            //因为此时stackInTop的位置是即将插入的位置,但其实是没有元素在这个位置上的,所以应该用--stackInTop
            obj->stackOut[stackOutTop++] = obj->stackIn[--stackInTop];
        }
    }
    //第二个栈出栈
    int top = obj->stackOut[--stackOutTop];
    //将输出栈中元素放回输入栈中
    while(stackOutTop) 
    {
        obj->stackIn[stackInTop++] = obj->stackOut[--stackOutTop];
    }
    //更新栈顶指针
    obj->stackInTop = stackInTop;
    obj->stackOutTop = stackOutTop;
    //返回队列中第一个元素
    return top;
}

int myQueuePeek(MyQueue* obj) {
    return obj->stackIn[0];
}

bool myQueueEmpty(MyQueue* obj) {
    return !obj->stackInTop && !obj->stackOutTop;
}

void myQueueFree(MyQueue* obj) {
    obj->stackInTop = 0;
    obj->stackOutTop = 0;
}

/**
 1. Your MyQueue struct will be instantiated and called as such:
 2. MyQueue* obj = myQueueCreate();
 3. myQueuePush(obj, x);
 
 4. int param_2 = myQueuePop(obj);
 
 5. int param_3 = myQueuePeek(obj);
 
 6. bool param_4 = myQueueEmpty(obj);
 
 7. myQueueFree(obj);
*/

从这道题就可以看出,用C++的话,有各种STL自带的比如push、pop、empty等会简便很多!!!!
stack有支持push、top、pop,可以直接使用来做到入栈、查栈顶以及出栈!

用队列实现栈

例题225(简单)用队列实现栈

注意要点:

  1. 通过两个队列,一个控制入栈元素,另一个作为出栈缓冲,把除队尾元素外都复制入队,最后在出栈完成时,重新拷贝回到入栈的队列。

下面贴出代码:

CPP版本

class MyStack {
public:
    queue<int> que1;
    queue<int> que2;
    MyStack() {

    }
    
    void push(int x) {
        que1.push(x);
    }
    
    int pop() {
        int size = que1.size();
        int result = que1.back();
        size--;
        while (size--)
        {
            que2.push(que1.front());
            que1.pop();
        }
        //此时只剩下队尾元素,即所求栈顶
        que1.pop();
        que1 = que2;
        while (!que2.empty()) {que2.pop();}
        return result;
    }
    
    int top() {
        return que1.back();
    }
    
    bool empty() {
        return que1.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();
 */

C版本

typedef struct {
    int queue1[100], queue2[100];
    int head1, head2;
    int tail1, tail2;
} MyStack;

MyStack* myStackCreate() {
    MyStack* stack = (MyStack* )malloc(sizeof(MyStack));
    stack->head1 = 0, stack->head2 = 0;
    stack->tail1 = 0, stack->tail2 = 0;
    return stack;
}

void myStackPush(MyStack* obj, int x) {
    obj->queue1[(obj->tail1)++] = x;
}

int myStackPop(MyStack* obj) {
    int head1 = obj->head1, head2 = obj->head2;
    int tail1 = obj->tail1, tail2 = obj->tail2;
    //除了最后一个元素,全部复制进入queue2
    while ((tail1 - head1) > 1)
    {
        obj->queue2[tail2++] = obj->queue1[head1++];
    }
    //queue1最后一个元素出队
    int top = obj->queue1[head1++];
    //queue2全部再次进入queue1
    while (tail2 - head2)
    {
        obj->queue1[tail1++] = obj->queue2[head2++];
    }

    obj->head1 = head1, obj->tail1 = tail1;
    obj->head2 = head2, obj->tail2 = tail2;

    return top;
}

int myStackTop(MyStack* obj) {
    return obj->queue1[obj->tail1 - 1];
}

bool myStackEmpty(MyStack* obj) {
    return obj->tail1 == obj->head1;
}

void myStackFree(MyStack* obj) {
    obj->head1 = 0, obj->tail1 = 0;
    obj->head2 = 0, obj->tail2 = 0;
}

/**
 1. Your MyStack struct will be instantiated and called as such:
 2. MyStack* obj = myStackCreate();
 3. myStackPush(obj, x);
 
 4. int param_2 = myStackPop(obj);
 
 5. int param_3 = myStackTop(obj);
 
 6. bool param_4 = myStackEmpty(obj);
 
 7. myStackFree(obj);
*/

再次可以看出,用C++有了STL之后,操作有多简单!
C++中,queue支持push、pop、front、back、empty,可以入队、出队、直接查询队列头元素和尾元素、判断是否是空队列!

有效的括号

例题20(简单)有效的括号

注意要点:

  1. 遇到这种有顺序的匹配,就可以考虑用栈来实现;
  2. 遇到左括号,就入栈右括号;
  3. 如果是匹配的右括号,就可以出栈;
  4. 如果遇到入栈是右括号但栈已经空,或者左右不匹配就false;
  5. 退出循环后栈非空则证明并不匹配。

下面贴出代码:

CPP版本

class Solution {
public:
    bool isValid(string s) {
        if (s.size() % 2) {return 0;}
        stack<char> stk;
        for (char str : s)
        {
            if (str == '(') {stk.push(')');}
            else if (str == '[') {stk.push(']');}
            else if (str == '{') {stk.push('}');}
            else if (stk.empty() || stk.top() != str) {return 0;}
            else {stk.pop();}
        }
        return stk.empty();
    }
};

C版本

//辅助函数:判断栈顶元素与输入的括号是否为一对。若不是,则返回False
int notMatch(char par, char* stack, int stackTop) {
    switch(par) 
    {
        case ']': return stack[stackTop - 1] != '[';
        case ')': return stack[stackTop - 1] != '(';
        case '}': return stack[stackTop - 1] != '{';
    }
    return 0;
}

bool isValid(char * s){
    int len = strlen(s);
    char* stack = (char* )malloc(sizeof(char) * len);
    int stkTop = 0;
    for (int i = 0; i < len; i++)
    {
        if (s[i] == '(' || s[i] == '[' || s[i] == '{') {stack[stkTop++] = s[i];}
        else
        {
            if (!stkTop || notMatch(s[i], stack, stkTop)) {return 0;}
            stkTop--;
        }
    }
    return !stkTop;
}

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

例题1047(简单)删除字符串中的所有相邻重复项

注意要点:

  1. 删除后相邻也要删除,所以很适合用栈来实现;
  2. 需要注意入栈后,出栈会导致字符串反向,所以要么倒序遍历,要么出栈后reverse。

下面贴出代码:

CPP版本

class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> stk;
        for (int i = s.size() - 1; i >= 0; i--)
        {
            if (!stk.empty() && stk.top() == s[i]) {stk.pop();}
            else {stk.push(s[i]);}
        }
        string str = "";
        while (!stk.empty())
        {
            str += stk.top();
            stk.pop();
        }
        return str;
    }
};

C版本

char * removeDuplicates(char * s){
    int len = strlen(s) + 1;
    char* stk = (char* )malloc(sizeof(char) * len);
    int stkTop = 0;
    for (int i = 0; i < len - 1; i++)
    {
        if (stkTop && stk[stkTop - 1] == s[i]) {stkTop--;}
        else {stk[stkTop++] = s[i];}
    }
    stk[stkTop] = '\0';
    return stk;
}

逆波兰表达式求值

例题150(中等)逆波兰表达式求值

注意要点:

  1. 只需要记住用栈来实现,碰到计算符号,就取出栈顶的两个元素相应计算。

下面贴出代码:

CPP版本

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long> stk;
        for (int i = 0; i < tokens.size(); i++)
        {
            if (tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/")
            {
                long long num2 = stk.top();
                stk.pop();
                long long num1 = stk.top();
                stk.pop();
                if (tokens[i] == "+") {stk.push(num1 + num2);}
                if (tokens[i] == "-") {stk.push(num1 - num2);}
                if (tokens[i] == "*") {stk.push(num1 * num2);}
                if (tokens[i] == "/") {stk.push(num1 / num2);}
            }
            else {stk.push(stoll(tokens[i]));}
        }
        return stk.top();
    }
};

C版本

bool isSign(char* s)
{
    if (strlen(s) == 1 &&(s[0] == '+' || s[0] == '-' || s[0] == '*' || s[0] == '/'))
    {
        return true;
    }
    else return false;
}

int evalRPN(char ** tokens, int tokensSize){
    int* stack = (int* )malloc(sizeof(int) * tokensSize);
    int top = 0;
    for (int i = 0; i < tokensSize; i++)
    {
        char* str = tokens[i];
        if (!isSign(str))
        {
            stack[top++] = atoi(str);
        }
        else
        {
            int num2 = stack[--top];
            int num1 = stack[--top];
            switch(str[0])
            {
                case '+':
                    stack[top++] = num1 + num2;
                    break;
                case '-':
                    stack[top++] = num1 - num2;
                    break;
                case '*':
                    stack[top++] = num1 * num2;
                    break;
                case '/':
                    stack[top++] = num1 / num2;
                    break;
            }
        }
    }
    return stack[top - 1];
}

滑动窗口最大值

例题239(困难)滑动窗口最大值

注意要点:

  1. 通过自行创建辅助队列来进行判断:只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的,也就是一个单调队列
  2. pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作;
  3. push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出直到push元素的数值小于等于队列入口元素的数值为止。

下面贴出代码:

CPP版本

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> ret;
        for (int i = 0; i < k; i++) {que.push(nums[i]);}
        ret.push_back(que.front());
        for (int i = k; i < nums.size(); i++)
        {
            que.pop(nums[i - k]);  // 滑动窗口移除最前面元素
            que.push(nums[i]);  // 滑动窗口加入最后面元素
            ret.push_back(que.front());  //记录对应最大值
        }
        return ret;
    }

private:
    class MyQueue
    {
        public:
            deque<int> que;
            // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,相等则弹出。
            // 同时pop之前判断队列当前是否为空。
            void pop(int value)
            {
                if (!que.empty() && value == que.front()) {que.pop_front();}
            }
             // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
            // 这样就保持了队列里的数值是单调从大到小的了。
            void push(int value)
            {
                while (!que.empty() && value > que.back()) {que.pop_back();}
                que.push_back(value);
            }
            // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
            int front() {return que.front();}
    };
};

C版本

/**
 1. Note: The returned array must be malloced, assume caller calls free().
 */
int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize){
    int* queue = (int* )malloc(sizeof(int) * numsSize);
    int left = 0, right = 0;
    for (int i = 0; i < k; i++)
    {
        while (left < right && nums[i] > nums[queue[right - 1]]) {right--;}
        queue[right++] = i;
    }
    *returnSize = 0;
    int* ans = (int* )malloc(sizeof(int) * (numsSize - k + 1));
    ans[(*returnSize)++] = nums[queue[left]];
    for (int i = k; i < numsSize; i++)
    {
        while (left < right && nums[i] > nums[queue[right - 1]]) {right--;}
        queue[right++] = i;
        while (queue[left] <= i - k) {left++;}
        ans[(*returnSize)++] = nums[queue[left]];
    }
    return ans;
}

对于底层实现deque这个双向队列而言,可以从两端front和back以及pop和push,只要变成push_back和pop_front就可以了。

前k个高频元素

例题347(中等)前k个高频元素

注意要点:

  1. 对频率进行排序,这里我们可以使用一种容器适配器就是优先级队列,其本质就是一个堆,堆就是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值,父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆;
  2. 用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素;
  3. 缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)

下面贴出代码:

CPP版本

class Solution {
public:
    class Mycomparison
    {
        public:
        //通过排序变成小顶堆
            bool operator() (const pair<int, int>& l, const pair<int, int>& r)
            {
                return l.second > r.second;
            }
    };

    vector<int> topKFrequent(vector<int>& nums, int k) {
        //map统计频率
        unordered_map<int, int> map;
        for (int i = 0; i < nums.size(); i++) {map[nums[i]]++;}
        //定义一个大小为k的小顶堆
        priority_queue<pair<int, int>, vector<pair<int, int>>, Mycomparison> pri_que;
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++)
        {
            pri_que.push(*it);
            if (pri_que.size() > k) {pri_que.pop();}
        }
        //反向输出小顶堆,则得到从大到小的前k个
        vector<int> ans(k);
        for (int i = k - 1; i >= 0; i--)
        {
            ans[i] = pri_que.top().first;
            pri_que.pop();
        }
        return ans;
    }
};

C版本过于复杂,就没有花精力去写了……

总结

  1. 从本章开始,可以明显感受到有STL的C++操作起来简单很多,各种插入和弹出元素都有STL已经实现,只需要关注逻辑代码;C则需要自行维护一个数组,通过栈和队列的定义自行操作指针来实现模拟出入栈和出入队列;
  2. 难点在于最后两题,一个自行实现单调队列,一个则借助了优先级队列的概念;
  3. C++中,queue和stack都支持push、pop、top;如果从底层出发,则可以直接使用deque,两头都可以操作,只要用形如x_back和x_front即可,同时top则替换成front和back
  4. 栈和队列在二叉树的遍历之中非常常用,是基础的数据结构;
  5. 本章练习题主要关注239和347,需要经常看看,不然就忘了……
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值