LeetCode队列和栈
数据结构与算法是今后学习、求职一直会用到的重要内容,鉴于我大一学的一团糟的惨状,我决定根据LeetCode专题的节奏来梳理一些重要的数据结构与算法,巩固一下自己的基础知识,也希望可以给没有好好学习数据结构与算法的小伙伴提个醒,快去刷题!
队列
队列是一种先进先出(FIFO)的数据结构,插入操作称为入队(enqueue),删除操作称为出队(dequeue)。
为了实现队列,可以使用动态数组和指向队列头部的指针。但是这样的实现会导致队列出现假饱和现象,即经过不断的插入和删除元素操作后,虽然队列是空的,但已经无法再插入新的元素。因此,在实际使用中,我们会设计循环队列,通过固定大小的数组和两个指针来指示起始位置和结束位置。
接下来我们实现自己的循环队列:
class MyCircularQueue {
private:
vector<int> data;
int head, tail, length;
public:
/** Initialize your data structure here. Set the size of the queue to be k. */
MyCircularQueue(int k) {
head = tail = -1;
length = k;
data.resize(k);
}
/** Insert an element into the circular queue. Return true if the operation is successful. */
bool enQueue(int value) {
if (isFull())
return false;
tail = (tail + 1) % length;
data[tail] = value;
if (tail == 0 && head == -1) // first element
head = 0;
return true;
}
/** Delete an element from the circular queue. Return true if the operation is successful. */
bool deQueue() {
if (isEmpty())
return false;
head = (head + 1) % length;
if (head == (tail + 1) % length) // last element
head = tail = -1;
return true;
}
/** Get the front item from the queue. */
int Front() {
if (isEmpty())
return -1;
return data[head];
}
/** Get the last item from the queue. */
int Rear() {
if (isEmpty())
return -1;
return data[tail];
}
/** Checks whether the circular queue is empty or not. */
bool isEmpty() {
if (tail == -1)
return true;
return false;
}
/** Checks whether the circular queue is full or not. */
bool isFull() {
if ((tail + 1) % length == head)
return true;
return false;
}
};
/**
* 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();
*/
函数 | 作用 |
---|---|
q.push(x) | 将元素x添加到队列的末尾 |
q.pop() | 删除队列的第一个元素,并不返回删除的元素 |
q.front() | 返回队列的第一个元素 |
q.back() | 返回队列的最后一个元素 |
q.empty() | 当队列为空时返回true |
q.size() | 返回队列中元素的个数 |
广度优先搜索(BFS)
广度优先搜索最广泛的应用是找出从根节点到目标节点的最短路径,我们将根据节点到根节点的距离依次遍历,第一次找到目标节点时,就是根节点到目标节点的最短距离。节点的处理顺序与他们添加进处理队列的顺序相同,因此我们可以使用队列这样的数据结构来处理BFS问题。
BFS的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
}
其中used为一个哈希集,用来记录已经访问过的节点。当确定没有循环(如树的遍历)或者希望多次将同一节点加入队列中时,可以不使用哈希集。
为了更好地应用BFS算法,我们将看以下3个例题。
岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
这道题目既可以使用深度优先搜索,也可以使用广度优先搜索来做,为了熟悉BFS,我们展示BFS的解法如下:
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int row = grid.size();
if (row == 0)
return 0;
int col = grid[0].size();
queue<pair<int, int> > q;
int ans = 0;
for (int i = 0; i < row; ++i)
for(int j = 0; j < col; ++j){
if (grid[i][j] == '1'){
// a new island
ans += 1;
q.push(make_pair(i, j));
grid[i][j] = '0';
while (!q.empty()) {
// BFS searching for the neighbors
pair<int, int> pr = q.front();
q.pop();
int x = pr.first, y = pr.second;
if (x - 1 >= 0 && grid[x - 1][y] == '1') {
q.push(make_pair(x - 1, y));
grid[x - 1][y] = '0';
}
if (x + 1 < row && grid[x + 1][y] == '1') {
q.push(make_pair(x + 1, y));
grid[x + 1][y] = '0';
}
if (y - 1 >= 0 && grid[x][y - 1] == '1') {
q.push(make_pair(x, y - 1));
grid[x][y - 1] = '0';
}
if (y + 1 < col && grid[x][y + 1] == '1') {
q.push(make_pair(x, y + 1));
grid[x][y + 1] = '0';
}
}
}
}
return ans;
}
};
打开转盘锁
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
这道题相比于一般的BFS问题,增加了一个deadends集合,其中的状态不允许到达,那么我们可以简单地将deadends集合并入visited集合。由于这里会反复到达同一个状态,因此我们使用set来定义visited集合,以避免状态的重复添加。
class Solution {
public:
// check if it is a deadend number
bool is_deadend(vector<string>& deadends, string s){
int length = deadends.size();
for (int i = 0; i < length; ++i)
if (s == deadends[i])
return true;
return false;
}
int openLock(vector<string>& deadends, string target) {
if (is_deadend(deadends, "0000"))
return -1;
if (target == "0000")
return 0;
queue<string> q;
q.push("0000");
set<string> se; // record visited node, including deadends
se.insert("0000");
for (int i = 0; i < deadends.size(); ++i)
se.insert(deadends[i]);
int ans = 0;
while (!q.empty()){
ans += 1;
int num = q.size();
for (int i = 0; i < num; i++) {
string state = q.front