1、剑指 Offer 09. 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
class CQueue {
stack<int> stack1,stack2;
public:
CQueue() {
while (!stack1.empty()) {
stack1.pop();
}
while (!stack2.empty()) {
stack2.pop();
}
}
void appendTail(int value) {
stack1.push(value);
}
int deleteHead() {
// 如果第二个栈为空
if (stack2.empty()) {
while (!stack1.empty()) {// 数据从栈1存入栈2,这样在栈2栈顶元素为最先加入元素
stack2.push(stack1.top());
stack1.pop();
}
}
if (stack2.empty()) {
return -1;
} else {// 删除第一个加入的节点
int deleteItem = stack2.top();
stack2.pop();
return deleteItem;
}
}
};
使用队列实现栈的下列操作:
- push(x) -- 元素 x 入栈
- pop() -- 移除栈顶元素
- top() -- 获取栈顶元素
- empty() -- 返回栈是否为空
思路:
队列模拟栈,其实一个队列就够了,那么我们先说一说两个队列来实现栈的思路。
队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。
所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全是用来备份的!
如下面动画所示,用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
class MyStack {
public:
queue<int> que1;
queue<int> que2; // 辅助队列
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
que1.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int size = que1.size();
size--;
while (size--) { // 将que1 导入que2,但要留下最后一个元素
que2.push(que1.front());
que1.pop();
}
int result = que1.front(); // 留下的最后一个元素就是我们要返回的值
que1.pop();
que1 = que2; // 再将que2赋值给que1
while(!que2.empty()) { // 清空que2
que2.pop();
}
return result;
}
/** Get the top element. */
int top() {
return que1.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return que1.empty();
}
};
3、设计循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
示例:
MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1); // 返回 true
circularQueue.enQueue(2); // 返回 true
circularQueue.enQueue(3); // 返回 true
circularQueue.enQueue(4); // 返回 false,队列已满
circularQueue.Rear(); // 返回 3
circularQueue.isFull(); // 返回 true
circularQueue.deQueue(); // 返回 true
circularQueue.enQueue(4); // 返回 true
circularQueue.Rear(); // 返回 4
提示:
所有的值都在 0 至 1000 的范围内;
操作数将在 1 至 1000 的范围内;
请不要使用内置的队列库。
class MyCircularQueue {
int* data;
int size;
int head;
int tail;
int capicity;
public:
/** Initialize your data structure here. Set the size of the queue to be k. */
MyCircularQueue(int k) {
data = new int[k];
size = k;
head = 0;
tail = 0;
capicity = 0;
}
/** Insert an element into the circular queue. Return true if the operation is successful. */
bool enQueue(int value) {
if (capicity < size) {
data[tail++] = value;
++capicity;
if (tail == size) {
tail = 0;
}
return true;
}
return false;
}
/** Delete an element from the circular queue. Return true if the operation is successful. */
bool deQueue() {
if (capicity > 0) {
++head;
--capicity;
if (head == size) {
head = 0;
}
return true;
}
return false;
}
/** Get the front item from the queue. */
int Front() {
if (capicity == 0) {
return -1;
}
return data[head];
}
/** Get the last item from the queue. */
int Rear() {
if (capicity == 0) {
return -1;
}
return tail == 0 ? data[size - 1] : data[tail - 1];
}
/** Checks whether the circular queue is empty or not. */
bool isEmpty() {
return capicity == 0;
}
/** Checks whether the circular queue is full or not. */
bool isFull() {
return capicity == size;
}
};
/**
* 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();
*/
4、设计实现双端队列。
你的实现需要支持以下操作:
MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。
示例:
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1); // 返回 true
circularDeque.insertLast(2); // 返回 true
circularDeque.insertFront(3); // 返回 true
circularDeque.insertFront(4); // 已经满了,返回 false
circularDeque.getRear(); // 返回 2
circularDeque.isFull(); // 返回 true
circularDeque.deleteLast(); // 返回 true
circularDeque.insertFront(4); // 返回 true
circularDeque.getFront(); // 返回 4
提示:
所有值的范围为 [1, 1000]
操作次数的范围为 [1, 1000]
请不要使用内置的双端队列库。
解题思路
注意:
head是头部待出队位置(获取头元素,即直接访问arr[head])
从头插入,head是逆时针旋转(需要先减1)
从头删除,head是顺时针旋转
tail是尾部待入队位置,(获取尾元素, 需要先将tail逆时针旋转1)
从尾插入,tail是顺时针旋转
从尾删除,tail是逆时针旋转
class MyCircularDeque {
public:
/** Initialize your data structure here. Set the size of the deque to be k. */
MyCircularDeque(int k) {
m_head = 0;
m_tail = 0;
m_size = 0;
m_capacity = k;
m_arr = new int[k];
}
/** Adds an item at the front of Deque. Return true if the operation is successful. */
bool insertFront(int value) {
if (isFull())
return false;
//head指向当前元素, 前插时,head逆时针旋转
--m_head;
if (m_head < 0)
{
m_head = m_capacity - 1;
}
m_arr[m_head] = value;
++m_size;
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;
m_arr[m_tail++] = value;
if (m_tail == m_capacity)
{
m_tail = 0;
}
++m_size;
return true;
}
/** Deletes an item from the front of Deque. Return true if the operation is successful. */
bool deleteFront() {
if (isEmpty())
return false;
//从头删除 head顺时针旋转
++m_head;
if (m_head == m_capacity)
{
m_head = 0;
}
--m_size;
return true;
}
/** Deletes an item from the rear of Deque. Return true if the operation is successful. */
bool deleteLast() {
if (isEmpty())
return false;
--m_tail;
if (m_tail < 0)
{
m_tail = m_capacity - 1;
}
--m_size;
return true;
}
/** Get the front item from the deque. */
int getFront() {
if (isEmpty())
return -1;
return m_arr[m_head];
}
/** Get the last item from the deque. */
int getRear() {
if (isEmpty())
return -1;
//注意:最后元素的索引是m_tail的上一个元素
int index = m_tail - 1;
if (index < 0)
{
index = m_capacity - 1;
}
return m_arr[index];
}
/** Checks whether the circular deque is empty or not. */
bool isEmpty() {
return m_size == 0;
}
/** Checks whether the circular deque is full or not. */
bool isFull() {
return m_size == m_capacity;
}
private:
int* m_arr;
int m_head;
int m_tail;
int m_size; //元素个数
int m_capacity; //容量 固定长度
};
/**
* 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();
*/
设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。
注意:
总人数少于1100人。
示例
输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
核心思路非常简单:
先排身高更高的,这是要防止后排入人员影响先排入人员位置
每次排入新人员[h,k]时,已处于队列的人身高都>=h,所以新排入位置就是people[k]
## 身高降序排列,身高相同,人数升序排列,然后按照新的排序,人数作为排位位置进行插入;
分别用两种方式实现插入,list效率更高;
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
/*
* 1. 首先考虑身高相同的情况: 例如 [7,0] 和 [7,1], [7,0]一定在[7,1]前面
* 2. 然后接着考虑身高不同的情况, 身高低的相对于身高高的人来说, 是看不见的, 不影响k值
* 3. 所以可以按照 [h,k] h大小排序, h相同按照k的大小排序
* 4. [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] --- >->->
* [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]
* 5. 按照 [h,k]中k的位置在容器中插入:
* 5.1 -> [7,0], [7,1]
* 5.2 -> [7,0], [6,1], [7,1]
* 5.3 -> [5,0], [7,0], [6,1], [7,1]
* 5.4 -> [5,0], [7,0], [5,2], [6,1], [7,1]
* 5.5 -> [5,0], [7,0], [5,2], [6,1], [4,4], [7,1]
*/
//STL排序 sort 用法:sort(first_pointer,first_pointer+n,cmp),默认为升序,若要使用降序,自行写cmp 函数
auto cmp = [](const vector<int>& v1, const vector<int>& v2) {
//若第一个元素相同,则按照第二个元素升序排列,否则,按照第一个元素降序排列
return v1[0] == v2[0] ? v1[1] < v2[1] : v1[0] > v2[0];
};
//标准模版库算法实例化,身高降序排列,人数升序排列
sort(people.begin(), people.end(), cmp);
//按照顺序重新插入,按照人数的位置进行插入
//方法一:list 实现,效率更高:
// list<vector<int>> sort_list;
// for (auto item : people) {
// auto iter = sort_list.begin();
// //advance迭代器就是将迭代器it,移动n位。如果it是随机访问迭代器,那么函数进行1次运算符计算操作,否则函数将对迭代器进行n次迭代计算操作
// advance(iter, item[1]);
// sort_list.insert(iter, item);
// }
// //list转换为vector
// vector<vector<int>> ret(sort_list.begin(), sort_list.end());
// return ret;
//方法二:vector 实现:
vector<vector<int>> res;
for(auto item:people){
res.insert(res.begin()+item[1],item);
}
return res;
}
};
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.min(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.min(); --> 返回 -2.
思路:用一个辅助栈来专门维护栈的最小值
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
stack<int>s;
stack<int>Min;
//push操作,如果最小栈为空,则将元素入栈,否则比较栈顶元素和该元素大小确定是否入栈
void push(int x) {
s.push(x);
if(Min.empty()||x<=Min.top())Min.push(x);
}
//pop操作,如果原始栈不为空并且栈顶元素和和最小栈顶元素相同,则都出栈,否则只对原始栈出栈
void pop() {
if(!s.empty()){
if(s.top()==Min.top())Min.pop();
s.pop();
}
}
//top操作不变
int top() {
return s.top();
}
// min 函数就是返回最小栈栈顶元素
int min() {
return Min.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->min();
*/
栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。
该栈支持如下操作:push、pop、peek 和 isEmpty。当栈为空时,peek 返回 -1。
示例1:
输入: ["SortedStack", "push", "push", "peek", "pop", "peek"] [[], [1], [2], [], [], []] 输出: [null,null,null,1,null,2]
示例2:
输入: ["SortedStack", "pop", "pop", "push", "pop", "isEmpty"] [[], [], [], [1], [], []] 输出: [null,null,null,null,null,true]
解题思路:
利用辅助栈可以保证每次插入新元素的适合s1都是有序的,
比如 s1 = {1,5,6,8,9},插入 7
先把 9和8 插入 s2, s2 = {9,8}
再把 7 插入 s1, s1 = {1,5,6,7}
再把 s2 中数字插入 s1, s1 = {1,5,6,7,8,9}
这样思路最简单但是也比较麻烦
class SortedStack {
public:
stack<int> s1, s2;
SortedStack() {
}
//比较栈顶元素和将要入栈的元素,如果栈顶元素小于将入栈的元素,则出栈到辅助栈
void push(int val) {
while(!s1.empty() && s1.top() < val){
s2.push(s1.top());
s1.pop();
}
s1.push(val);
//然后将辅助栈元素再入栈道原始栈,此时栈有序,且栈顶为最小元素
while(!s2.empty()){
s1.push(s2.top());
s2.pop();
}
}
void pop() {
if(!s1.empty())
s1.pop();
}
int peek() {
if(!s1.empty())
return s1.top();
return -1;
}
bool isEmpty() {
return s1.empty();
}
};
/**
* Your SortedStack object will be instantiated and called as such:
* SortedStack* obj = new SortedStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->isEmpty();
*/
另一种解法,维护两个栈,原栈为降序,辅助栈为升序
比如s1 = {8, 7, 3} s2 = {}
插入 5,因为比s1.top大,把3插入s2中,然后 5插入 s1 中
s1 = {8,7,5} s2={3}
这样既能保证 s1 中的元素一定大于 s2 中元素,也可以使得两个栈都是按顺序排列
不必要像第一种解法一样需要在push的时候就把 s2 中元素重新加入到 s1 中去
class SortedStack {
public:
stack<int>s1;//原栈为降序
stack<int>s2;//辅助栈为升序
SortedStack() {
}
void push(int val) {
while(!s2.empty() && s2.top() > val){//辅助栈中存在比val大的值
s1.push(s2.top());
s2.pop();
}
while(!s1.empty() && s1.top() < val){//原栈中有比val小的值
s2.push(s1.top());
s1.pop();
}
s1.push(val);
}
void pop() {
while(!s2.empty()){//清空辅助栈
s1.push(s2.top());
s2.pop();
}
if(!s1.empty()) s1.pop();
}
int peek() {
while(!s2.empty()){//清空辅助栈
s1.push(s2.top());
s2.pop();
}
if(!s1.empty()) return s1.top();
else return -1;
}
bool isEmpty() {
return s1.empty() && s2.empty();
}
};
/**
* Your SortedStack object will be instantiated and called as such:
* SortedStack* obj = new SortedStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->isEmpty();
*/