【力扣】队列(先入先出)&& 栈(后入先出)
队列(先入先出)
按顺序处理数据
设计循环队列
#include <vector>
class MyCircularQueue {
public:
MyCircularQueue(int k) {
Queue.reserve(k);
size=0;
length=k;
head=-1;
tail=-1;
}
bool enQueue(int value) {
if(!isFull()){
size++;
if(isEmpty()){
tail++;
head++;
}
else tail+=1;
if(tail>=length) tail=0;
Queue[tail]=value;
return true;
}else return false;
}
bool deQueue() {
if(!isEmpty()){
size--;
if(head==tail){
head=-1;
tail=-1;
return true;
}
head+=1;
if(head>=length) head=0;
return true;
}else return false;
}
int Front() {
if(isEmpty()) return -1;
else return Queue[head];
}
int Rear() {
if(isEmpty()) return -1;
else return Queue[tail];
}
bool isEmpty() {
if(head==-1&&tail==-1) return true;
else return false;
}
bool isFull() {
if(size==length) return true;
else return false;
}
private:
vector<int> Queue;
int size;
int head;
int tail;
int length;
};
/**
* Your MyCircularQueue object will be instantiated and called as such:
* MyCircularQueue* obj = new MyCircularQueue(k);
* bool param_1 = obj->enQueue(value);
* bool param_2 = obj->deQueue();
* int param_3 = obj->Front();
* int param_4 = obj->Rear();
* bool param_5 = obj->isEmpty();
* bool param_6 = obj->isFull();
*/
广度优先搜索(Breadth First Search)
- 结点的处理顺序是什么?
在第一轮中,我们处理根结点。在第二轮中,我们处理根结点旁边的结点;在第三轮中,我们处理距根结点两步的结点;等等等等。
与树的层序遍历类似,越是接近根结点的结点将越早地遍历。
如果在第 k 轮中将结点 X 添加到队列中,则根结点与 X 之间的最短路径的长度恰好是 k。也就是说,第一次找到目标结点时,你已经处于最短路径中。
- 队列的入队和出队顺序是什么?
如上面的动画所示,我们首先将根结点排入队列。然后在每一轮中,我们逐个处理已经在队列中的结点,并将所有邻居添加到队列中。值得注意的是,新添加的节点不会立即遍历,而是在下一轮中处理。
结点的处理顺序与它们添加到队列的顺序是完全相同的顺序,即先进先出(FIFO)。这就是我们在 BFS 中使用队列的原因。
模板 I(Java)
/**
* Return the length of the shortest path between root and target node.
*/
int BFS(Node root, Node target) {
Queue<Node> queue; // store all nodes which are waiting to be processed
int step = 0; // number of steps neeeded from root to current node
// initialize
add root to queue;
// BFS
while (queue is not empty) {
step = step + 1;
// iterate the nodes which are already in the queue
int size = queue.size();
for (int i = 0; i < size; ++i) {
Node cur = the first node in queue;
return step if cur is target;
for (Node next : the neighbors of cur) {
add next to queue;
}
remove the first node from queue;
}
}
return -1; // there is no path from root to target
}
模板 II(Java)
有时,确保我们永远不会访问一个结点两次很重要。否则,我们可能陷入无限循环。如果是这样,我们可以在上面的代码中添加一个哈希集来解决这个问题。这是修改后的伪代码:
/**
* Return the length of the shortest path between root and target node.
*/
int BFS(Node root, Node target) {
Queue<Node> queue; // store all nodes which are waiting to be processed
Set<Node> used; // store all the used nodes
int step = 0; // number of steps neeeded from root to current node
// initialize
add root to queue;
add root to used;
// BFS
while (queue is not empty) {
step = step + 1;
// iterate the nodes which are already in the queue
int size = queue.size();
for (int i = 0; i < size; ++i) {
Node cur = the first node in queue;
return step if cur is target;
for (Node next : the neighbors of cur) {
if (next is not in used) {
add next to queue;
add next to used;
}
}
remove the first node from queue;
}
}
return -1; // there is no path from root to target
}
有两种情况你不需要使用哈希集:
- 你完全确定没有循环,例如,在树遍历中;
- 你确实希望多次将结点添加到队列中。
栈(后入先出)
模板
#include <iostream>
class MyStack {
private:
vector<int> data; // store elements
public:
/** Insert an element into the stack. */
void push(int x) {
data.push_back(x);
}
/** Checks whether the queue is empty or not. */
bool isEmpty() {
return data.empty();
}
/** Get the top item from the queue. */
int top() {
return data.back();
}
/** Delete an element from the queue. Return true if the operation is successful. */
bool pop() {
if (isEmpty()) {
return false;
}
data.pop_back();
return true;
}
};
int main() {
MyStack s;
s.push(1);
s.push(2);
s.push(3);
for (int i = 0; i < 4; ++i) {
if (!s.isEmpty()) {
cout << s.top() << endl;
}
cout << (s.pop() ? "true" : "false") << endl;
}
}
最小栈
解法一:用vector模拟栈
#include<vector>
#include<climits>
class MinStack {
public:
MinStack() {
min=INT_MAX;
}
void push(int val) {
mystack.push_back(val);
}
void pop() {
if(!mystack.empty()) mystack.pop_back();
}
int top() {
if(!mystack.empty()) return mystack.back();
else return 0;
}
int getMin() {
if(!mystack.empty()){
min=mystack[0];
for(int i=0;i<mystack.size();i++){
if(mystack[i]<min) min=mystack[i];
}
}
return min;
}
private:
vector<int> mystack;
int min;
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
不足:效率很低!
解法二:双栈解决问题
#include<stack>
#include<climits>
class MinStack {
public:
MinStack() {
}
void push(int val) {
mystack.push(val);
if(stack2.empty()||val<=stack2.top()) stack2.push(val);
}
void pop() {
if(!stack2.empty()&&mystack.top()==stack2.top()){
stack2.pop();
}
if(!mystack.empty()) mystack.pop();
}
int top() {
if(!mystack.empty()) return mystack.top();
else return 0;
}
int getMin() {
if(stack2.size()) return stack2.top();
else return 0;
}
private:
stack<int> mystack;
stack<int> stack2;
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
另一个栈储存最小值
括号对称
#include<stack>
#include<string>
class Solution {
public:
bool isValid(string s) {
if(s.length()%2) return false;
for(int i=0;i<s.length();i++){
if(s[i]=='{')stack1.push('}');
else if(s[i]=='[') stack1.push(']');
else if(s[i]=='(') stack1.push(')');
else if(stack1.empty()||s[i]!=stack1.top()){
return false;
}else stack1.pop();
}
if(!stack1.empty()) return false;
else return true;
}
private:
stack<char>stack1;
};
每日温度
暴力解法效率极低,易超时
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> answer;
answer.resize(temperatures.size());//resize可以调整capacity
//cout<<answer.capacity()<<endl;
int k=0;
for(int i=0;i<temperatures.size();i++){
int cnt=0;
for(int j=i+1;j<temperatures.size();j++){
if(temperatures[j]>temperatures[i])
{answer[k]=j-i;k++;cnt=1;break;}
}
if(cnt==0){answer[k]=0;k++;}
}
answer[temperatures.size()-1]=0;
return answer;
}
};
用单栈解决:
#include<stack>
#include<vector>
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
stack<int> mystack;
vector<int> answer(T.size());
for(int i=0;i<T.size();i++){
if(mystack.empty()||T[i]<=T[mystack.top()]){
mystack.push(i);
}else{
while(!mystack.empty()){
if(T[mystack.top()]>=T[i]) {mystack.push(i);break;}
answer[mystack.top()]=i-mystack.top();
mystack.pop();
}
mystack.push(i);
}
}
return answer;
}
};
逆波兰表达式
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> Stack;
for(int i=0;i<tokens.size();i++){
if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="/"||tokens[i]=="*"){
long long a=Stack.top();
Stack.pop();
long long b=Stack.top();
Stack.pop();
if(tokens[i]=="+") Stack.push(a+b);
else if(tokens[i]=="-") Stack.push(b-a);
else if(tokens[i]=="/") Stack.push(b/a);
else if(tokens[i]=="*") Stack.push(b*a);
}else{
int a=stoi(tokens[i]);
Stack.push(a);
}
}
return Stack.top();
}
};
DFS(深度优先搜索)
DFS - 模板 I
正如我们在本章的描述中提到的,在大多数情况下,我们在能使用 BFS 时也可以使用 DFS。但是有一个重要的区别:遍历顺序。
与 BFS 不同,更早访问的结点可能不是更靠近根结点的结点。因此,你在 DFS 中找到的第一条路径可能不是最短路径。
/*
* Return true if there is a path from cur to target.
*/
boolean DFS(Node cur, Node target, Set<Node> visited) {
return true if cur is target;
for (next : each neighbor of cur) {
if (next is not in visited) {
add next to visted;
return true if DFS(next, target, visited) == true;
}
}
return false;
}
DFS - 模板 II
递归解决方案的优点是它更容易实现。 但是,存在一个很大的缺点:如果递归的深度太高,你将遭受堆栈溢出。 在这种情况下,您可能会希望使用 BFS,或使用显式栈实现 DFS。
/*
* Return true if there is a path from cur to target.
*/
boolean DFS(int root, int target) {
Set<Node> visited;
Stack<Node> s;
add root to s;
while (s is not empty) {
Node cur = the top element in s;
return true if cur is target;
for (Node next : the neighbors of cur) {
if (next is not in visited) {
add next to s;
add next to visited;
}
}
remove cur from s;
}
return false;
}
小结
小结
在前面的章节中,我们介绍了两个数据结构:队列和栈
-
队列
队列是一种 FIFO 式的数据结构:第一个元素将被首先处理。有两个重要操作:入队和出队。我们可以使用带有两个指针的动态数组来实现队列。
我们可以使用广度优先搜索(BFS)。队列还有一些重要的扩展,例如:
- 双端队列
- 优先队列 -
栈
栈是一种 LIFO 式的数据结构:最后一个元素将被首先处理。有两个重要操作:push 和 pop。栈的实现非常简单,使用动态数组就足以实现栈。
当满足 LIFO 原则时,我们使用栈。深度优先搜索(DFS)是栈的一个重要应用。 -
总结
理解和比较以下几组概念:
- FIFO 和 LIFO;
- 队列 和 栈;
- BFS 和 DFS。