理论:
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 是数组大小。