文章目录
Part.I Attention
- deque 是 double-ended queue 的缩写,意即双端队列,详细信息参见官网。
deque<T>
容器适配器是- 必须要包含头文件
#include <deque>
deque
相较于vector
:①它不擅长在中间添加删除元素;②它擅长在队列头部和尾部添加删除元素,时间复杂度为O(1)
,而vector
只有在尾部添加删除元素的时间复杂度才为O(1)
。deque
相较于queue
:queue只擅长在头部删除元素,在尾部添加元素;而deque在头部和尾部都擅长添加或删除元素。- C++ 双向队列用的并不是很多,一般在刷题的时候常用
vector
。但是它也是STL中的一种比较典型的数据结构,所以在实际生产中也可以考虑使用它。
Part.II Funciton
deque
的函数相较于queue
就多很多了,和Vector
相当,如下图:
下面是对常用函数的说明:
函数 | 解释 |
---|---|
begin() |
开始的迭代器 |
end() |
结束的迭代器 |
rbegin() |
反向开始的迭代器 |
rend() |
反向结束的迭代器 |
size() |
所含元素个数 |
max_size |
最大能容纳的元素个数 |
resize() |
重新调整其大小 |
empty() |
判断其是否是空的 |
front() |
最前面的元素 |
back() |
最后面的元素 |
assign() |
可用它通过数组来对其进行赋值 |
push_back() |
在双端队列后面添加元素 |
push_front() |
在双端队列前面添加元素 |
pop_back() |
弹出双端队列最后一个元素 |
pop_front() |
弹出双端队列最前面一个元素 |
insert() |
插入元素,有好几种用法 |
erase(d.begin(),d.begin()+3) |
删除前面3个元素 |
a.swap(b) |
交换a 和b |
clear() |
清空所有元素 |
emplace(itr,a) |
在迭代器itr 位置前插入元素a ,返回a 的迭代器 |
emplace_front() |
在前面插入元素 |
emplace_back() |
在后面插入元素 |
Part.III Code
下面根据一道 LeetCode 上面的编程题 剑指 Offer II 026. 重排链表 来介绍双端队列的使用。
Chap.I 题目描述与分析
题目描述
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
分析
根据题意,需要将原链表按照前后交错的形式重新排列;笔者立刻就想到了双端队列,思路如下:
- 首先遍历一边原链表,将所有的结点都存到双端队列中
- 然后顺次从头部取一个,从尾部取一个
- 直到队列中不存在节点为止。
- 时间复杂度
O(n)
,空间复杂度O(n)
Chap.II 代码实现
双端队列C++
代码。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
void reorderList(ListNode* head) {
deque<ListNode*> dqu;
while(head) {
dqu.emplace_back(head);
head=head->next;
}
ListNode* pre=nullptr;
while(dqu.size()>=2) {
if(!pre) {
pre=dqu.front();
}
else {
pre->next=dqu.front();
pre=pre->next;
}
pre->next=dqu.back();
pre=pre->next;
dqu.pop_front();dqu.pop_back();
}
if(!dqu.empty()) {
if(!pre) pre=dqu.front();
else {
pre->next=dqu.front();
pre=pre->next;
}
}
pre->next=nullptr;
}
};
可以作为一个使用双端队列的示例,但是对于此题,使用双端队列并不一定是最优解。
Chap.III 两一种解题思路
下面的一种思路效率就比较高:
- 利用两个指针,一个是快指针(一次走两个节点),另一个是慢指针(一次走一个节点);慢指针边走边翻转链表,用一个
pre
做中间变量。 - 当快指针都到链表尾部时,快指针恰好指向链表的中间
- 此时快指针指向
pre
,慢指针继续向前走,边走边交叉构建结果即可。
下面是代码实现(写的有点乱)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
void reorderList(ListNode* head) {
if(!head||!head->next) return;
int tag=0; // 针对节点个数奇偶涉及的标识 奇1偶0
ListNode* slow=head, *fast=head, *pre=nullptr;
while(fast) {
if(fast->next) {
fast=fast->next;
ListNode* tmp=slow->next;
slow->next=pre; pre=slow;
slow=tmp;
}
else {
tag=1; break; }
if(fast->next) {
fast=fast->next;
}
else break;
}
fast=nullptr;
head=slow; // slow 就是中点
slow=slow->next;
head->next=fast;
if(tag) {
fast=slow; slow=slow->next; fast->next=head; head=fast; }
while(slow) {
fast=pre; pre=pre->next; fast->next=head; head=fast;
fast=slow; slow=slow->next; fast->next=head; head=fast;
}
if(pre) pre->next=head; head=pre;
}
};
Part.IV 双端队列的实现
下面一部分是双端队列的底层实现思想,以 LeetCode 641. 设计循环双端队列 题目为例。
Chap.I 题目描述
在不用deque
类的前提下,设计实现双端队列。
实现 MyCircularDeque 类:
MyCircularDeque(int k)
:构造函数,双端队列最大为 k 。boolean insertFront()
:将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false 。boolean insertLast()
:将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false 。boolean deleteFront()
:从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false 。boolean deleteLast()
:从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false 。int getFront()
:从双端队列头部获得一个元素。如果双端队列为空,返回 -1 。int getRear()
:获得双端队列的最后一个元素。 如果双端队列为空,返回 -1 。boolean isEmpty()
:若双端队列为空,则返回 true ,否则返回 false 。boolean isFull()
:若双端队列满了,则返回 true ,否则返回 false 。
Chap.II 代码实现
这道题和 662. 设计循环队列 很相似。可以看 题解
#include <iostream>
#include <vector>
using namespace std;
class MyCircularDeque {
private:
vector<int> arr;
int front;
int rear;
int capacity;
public:
/** Initialize your data structure here. Set the size of the deque to be k. */
MyCircularDeque(int k) {
capacity = k + 1;
arr.assign(capacity, 0);
front = 0;
rear = 0;
}
/** Adds an item at the front of Deque. Return true if the operation is successful. */
bool insertFront(int value) {
if (isFull()) {
return false;
}
front = (front - 1 + capacity) % capacity;
arr[front] = value;
return true;
}
/** Adds an item at the rear of Deque. Return true if the operation is successful. */
bool insertLast(int value) {
if (isFull()) {
return false;
}
arr[rear] = value;
rear = (rear + 1) % capacity;
return true;
}
/** Deletes an item from the front of Deque. Return true if the operation is successful. */
bool deleteFront() {
if (isEmpty()) {
return false;
}
// front 被设计在数组的开头,所以是 +1
front = (front + 1) % capacity;
return true;
}
/** Deletes an item from the rear of Deque. Return true if the operation is successful. */
bool deleteLast() {
if (isEmpty()) {
return false;
}
// rear 被设计在数组的末尾,所以是 -1
rear = (rear - 1 + capacity) % capacity;
return true;
}
/** Get the front item from the deque. */
int getFront() {
if (isEmpty()) {
return -1;
}
return arr[front];
}
/** Get the last item from the deque. */
int getRear() {
if (isEmpty()) {
return -1;
}
// 当 rear 为 0 时防止数组越界
return arr[(rear - 1 + capacity) % capacity];
}
/** Checks whether the circular deque is empty or not. */
bool isEmpty() {
return front == rear;
}
/** Checks whether the circular deque is full or not. */
bool isFull() {
// 注意:这个设计是非常经典的做法
return (rear + 1) % capacity == front;
}
};
/**
* Your MyCircularDeque object will be instantiated and called as such:
* MyCircularDeque* obj = new MyCircularDeque(k);
* bool param_1 = obj->insertFront(value);
* bool param_2 = obj->insertLast(value);
* bool param_3 = obj->deleteFront();
* bool param_4 = obj->deleteLast();
* int param_5 = obj->getFront();
* int param_6 = obj->getRear();
* bool param_7 = obj->isEmpty();
* bool param_8 = obj->isFull();
*/