DS | 栈与队列 Stack & Queue(240221更新)

理论:

1、栈
1.1 概念

        栈是一种具有特定限制的线性数据结构,遵循后进先出(LIFO,Last In First Out)的原则。栈可以看作是一个容器,只能在一端(栈顶)进行插入和删除操作,另一端(栈底)是固定的。新元素总是被插入到栈顶,而只能从栈顶移除元素。

栈的操作包括:
Push:将元素压入栈顶。
Pop:从栈顶弹出一个元素。
Peek:查看栈顶的元素,但不将其移除。
IsEmpty:检查栈是否为空。
IsFull:检查栈是否已满(如果底层存储是固定大小的)。
Clear:清空栈。

1.2 栈的存储结构及实现

1、顺序存储

线性表的顺序存储的简化;
基于数组,下标0的一端设为栈底(首元素都存在栈底,变化最小),设top变量指示栈顶元素在数组中的位置(top < stacksize)

c#实现:

public class ArrayStack<T>
{
    private T[] elements;
    private int top;
    private int maxSize;

    // 初始化操作,建立一个空栈S。
    public ArrayStack(int size)
    {
        maxSize = size;
        elements = new T[maxSize];
        top = -1;
    }

    // 若栈存在,则销毁它。
    public void DestroyStack()
    {
        elements = null;
        top = -1;
    }

    // 将栈清空。
    public void ClearStack()
    {
        top = -1;
    }

    // 若栈为空,返回true,否则返回false。
    public bool StackEmpty()
    {
        return top == -1;
    }

    // 若栈存在且非空,用e返回S的顶元素。
    public bool GetTop(out T e)
    {
        if (StackEmpty())
        {
            e = default(T);
            return false;
        }
        e = elements[top];
        return true;
    }

    // 若栈S存在,插入新元素e到栈S中并成为栈顶元素。
    public bool Push(T e)
    {
        if (top == maxSize - 1)
        {
            Console.WriteLine("Stack Overflow");
            return false;
        }
        elements[++top] = e;
        return true;
    }

    // 删除栈S中栈顶元素,并用e返回其值。
    public bool Pop(out T e)
    {
        if (StackEmpty())
        {
            Console.WriteLine("Stack Underflow");
            e = default(T);
            return false;
        }
        e = elements[top--];
        return true;
    }

    // 返回栈S的元素个数。
    public int StackLength()
    {
        return top + 1;
    }
}

2、链式存储

1.3 栈的应用

递归;四则运算表达式求值

1.4 c#:Stack

2、队列
2.1 概念

        队列是一种具有特定限制的线性数据结构,遵循先进先出(FIFO,First In First Out)的原则。队列可以看作是一个容器,只能在一端(队尾)进行插入操作,另一端(队首)进行删除操作。新元素总是被插入到队尾,而只能从队首移除元素。

队列的操作包括:
Enqueue:将元素加入到队尾。
Dequeue:从队首移除一个元素。
Peek:查看队首的元素,但不将其移除。
IsEmpty:检查队列是否为空。
IsFull:检查队列是否已满(如果底层存储是固定大小的)。
Clear:清空队列。

2.2 

练习:

232.用栈实现队列
1、题目

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false

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

示例 :

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

2、解题
public class MyQueue {
    private Stack<int> inputStack;
    private Stack<int> outputStack;
        
    public MyQueue() {
        inputStack = new Stack<int>();
        outputStack = new Stack<int>();
    }
    
    public void Push(int x) {
        inputStack.Push(x);
    }
    
    public int Pop() {
        if(outputStack.Count == 0)
        {
            while(inputStack.Count > 0)
            {
                outputStack.Push(inputStack.Pop());
            }
        }
        return outputStack.Pop();
    }
    
    public int Peek() {
        if(outputStack.Count == 0)
        {
            while(inputStack.Count > 0)
            {
                outputStack.Push(inputStack.Pop());
            }
        }
        return outputStack.Peek();
    }
    
    public bool Empty() {
        return inputStack.Count + outputStack.Count == 0;
    }
3、总结 
225. 用队列实现栈

1、题目

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

提示:
1 <= x <= 9
最多调用100 次 push、pop、top 和 empty
每次调用 pop 和 top 都保证栈不为空

2、解题
public class MyStack {
    private Queue<int> queue;
    private Queue<int> queue2;
    private int topElement;

    public MyStack() {
        queue = new Queue<int>();
        queue2 = new Queue<int>();
    }
    
    public void Push(int x) {
        queue.Enqueue(x);
        topElement = x;
    }
    
    public int Pop() {
        while(queue.Count > 1)
        {
            topElement = queue.Dequeue();
            queue2.Enqueue(topElement);
        }
        int result = queue.Dequeue();

        Queue<int> temp = queue;
        queue = queue2;
        queue2 = temp;

        return result;
    }
    
    public int Top() {
        return topElement;
    }
    
    public bool Empty() {
        return queue.Count == 0;
    }
}
3、总结

注意:  
        栈里需要定义topElement,做各种操作时需要及时更新
一个队列模拟解法:

using System;
using System.Collections.Generic;

public class MyStack
{
    private Queue<int> queue;

    /** Initialize your data structure here. */
    public MyStack()
    {
        queue = new Queue<int>();
    }

    /** Push element x onto stack. */
    public void Push(int x)
    {
        queue.Enqueue(x);
        int size = queue.Count;
        // 将新元素移到队列头部
        for (int i = 0; i < size - 1; i++)
        {
            queue.Enqueue(queue.Dequeue());
        }
    }

    /** Removes the element on top of the stack and returns that element. */
    public int Pop()
    {
        return queue.Dequeue();
    }

    /** Get the top element. */
    public int Top()
    {
        return queue.Peek();
    }

    /** Returns whether the stack is empty. */
    public bool Empty()
    {
        return queue.Count == 0;
    }
}
20. 有效的括号
1、题目

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

示例 :
输入:s = "()"
输出:true

2、解题
public class Solution {
    public bool IsValid(string s) {
        Stack<char> st = new Stack<char>();
        foreach(char c in s)
        {
            if(c == '(' || c == '[' || c == '{')
            {
                st.Push(c);
            }
            else
            {
                if(st.Count == 0)
                {
                    return false;
                }

                char top = st.Pop();
                if((c == ')' && top != '(')||
                   (c == '}' && top != '{')||
                   (c == ']' && top != '['))
                {
                    return false;
                }
            }
        }
        
        return st.Count == 0;
    }   
}   
3、总结

注意:
       
栈为空时使用Peek方法应用程序会抛出 InvalidOperationException 异常

思路:
        左括号进栈,右括号和栈顶元素比对,栈顶元素可能为空,可能不匹配,可能相消。

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

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

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

2、解题
public class Solution {
    public string RemoveDuplicates(string s) {
        Stack<char> st = new Stack<char>();
        foreach(char c in s)
        {
            if(st.Count != 0)
            {
                if(c == st.Peek())
                {
                    st.Pop();
                    continue;
                }
            }
            st.Push(c);
        }

        char[] chars = new char[st.Count];
        for(int i = chars.Length - 1; i >= 0; i --)
        {
            chars[i] = st.Pop();
        }

        return new string(chars);
    }
}
3、总结

注意:
        注意审题,是消除相邻重复字符,不是只消除与前一个字符相同的字符

StringBuilder解法:

using System;
using System.Text;

public class Solution {
    public string RemoveDuplicates(string S) {
        StringBuilder result = new StringBuilder();
        
        foreach (char c in S) {
            if (result.Length > 0 && result[result.Length - 1] == c) {
                result.Length--; // 删除 result 中最后一个字符
            } else {
                result.Append(c); // 将字符追加到 result
            }
        }
        
        return result.ToString();
    }
}

class Program {
    static void Main(string[] args) {
        Solution solution = new Solution();
        
        Console.WriteLine(solution.RemoveDuplicates("abbaca")); // 输出 "ca"
    }
}

150. 逆波兰表达式求值
1、题目

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。

注意:
有效的算符为 '+'、'-'、'*' 和 '/' 。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。

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

2、解题
public class Solution {
     public bool IsOperator(string token)
        {
            return token == "+" || token == "-" || token == "*" || token == "/";
        }

        public int PerformOperation(int operand1, int operand2, string op)
        {
            switch (op)
            {
                case "+" :
                    return operand1 + operand2;
                case "-" :
                    return operand1 - operand2;
                case "*" :
                    return operand1 * operand2;
                case "/" :
                    return operand1 / operand2;
                default :
                    return 0;
            }
        }
    public int EvalRPN(string[] tokens) {
        Stack<int> stack = new Stack<int>();

        foreach(string token in tokens)
        {
            if(IsOperator(token))
            {
                int operand2 = stack.Pop();
                int operand1 = stack.Pop();
                int result = PerformOperation(operand1, operand2, token);
                stack.Push(result);
            }
            else
            {
                stack.Push(int.Parse(token));
            }
        }

        return stack.Pop();
    }
}
3、总结

思路:
数字进栈,运算符两元素出栈运算,运算结果进栈

注意:
两元素出栈时注意运算顺序

239. 滑动窗口最大值 (一刷至少需要理解思路)
1、题目

给你一个整数数组 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

提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length

2、解题
public class Solution {
    public int[] MaxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.Length == 0 || k <= 0)
            return new int[0];
        
        List<int> result = new List<int>();
        LinkedList<int> deque = new LinkedList<int>();
        
        for (int i = 0; i < nums.Length; i++) {
            // 移除队列中不在滑动窗口范围内的元素
            if (deque.Count > 0 && deque.First.Value < i - k + 1)
                deque.RemoveFirst();
            
            // 移除队列中比当前元素小的元素,以保持队列头部为当前滑动窗口最大值
            while (deque.Count > 0 && nums[deque.Last.Value] < nums[i])
                deque.RemoveLast();
            
            // 将当前元素添加到队列尾部
            deque.AddLast(i);
            
            // 当窗口完全覆盖数组时,开始收集滑动窗口的最大值
            if (i >= k - 1)
                result.Add(nums[deque.First.Value]);
        }
        
        return result.ToArray();
    }
}
3、总结

注意:

思路:
双端队列:
        双端队列(Deque,即 Double-Ended Queue)是一种数据结构,它允许在队列两端进行插入和删除操作。既可以在队列的前端进行插入和删除操作(称为头部操作),也可以在队列的后端进行插入和删除操作(称为尾部操作)。
单调队列:
        单调队列是一种特殊的数据结构。单调队列可以支持在队列两端进行插入和删除操作,并且保持队列中元素的单调性。通常使用双端队列来实现。
        通常,单调队列用于解决一些滑动窗口类型的问题,特别是需要在滑动窗口中快速找到最大或最小值的情况。

347.前 K 个高频元素  (一刷至少需要理解思路)
1、题目

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

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

提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

2、解题
3、总结
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值