LeetCode-栈-队列-堆
文章目录
- LeetCode 225 - Implement Stack using Queues - 用队列实现栈 - easy
- LeetCode 232 - Implement Queue using Stack- 用栈实现队列 - easy
- LeetCode 155 - Min Stack - 最小栈 - easy
- Leetcode 946 - Validate Stack Sequences - 验证栈序列 - Medium
- LeetCode 224 - Basic Calculator - Hard
- LeetCode 215 - Kth Largest Element in an Array - 数组中第k大的数 - easy
- LeetCode 295 - Find Median from Data Stream - 寻找中位数 - Hard
LeetCode 225 - Implement Stack using Queues - 用队列实现栈 - easy
使用队列实现栈的下列操作:
push(x) -- 元素 x 入栈
pop() -- 移除栈顶元素
top() -- 获取栈顶元素
empty() -- 返回栈是否为空
注意:
你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。
题干要求,不能使用队列的back函数,也就是纯单向队列。
那么就需要借助临时队列。
class MyStack {
public:
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
// 构造临时队列
std::queue<int> queTmp;
queTmp.push(x);
int nTmp = 0;
while(!queData.empty())
{
nTmp = queData.front();
queTmp.push(nTmp);
queData.pop();
}
while(!queTmp.empty())
{
nTmp = queTmp.front();
queData.push(nTmp);
queTmp.pop();
}
//queData.swap(queTmp);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int nTmp = queData.front();
queData.pop();
return nTmp;
}
/** Get the top element. */
int top() {
return queData.front();
}
/** Returns whether the stack is empty. */
bool empty() {
return queData.empty();
}
private:
std::queue<int> queData;
};
不知道为啥,不用nTmp,直接push(front())
的话内存消耗要更大。
LeetCode 232 - Implement Queue using Stack- 用栈实现队列 - easy
使用栈实现队列的下列操作:
push(x) -- 将一个元素放入队列的尾部。
pop() -- 从队列首部移除元素。
peek() -- 返回队列首部的元素。
empty() -- 返回队列是否为空。
class MyQueue {
public:
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
std::stack<int> sTmp;
while(!sData.empty())
{
sTmp.push(sData.top());
sData.pop();
}
sData.push(x);
while(!sTmp.empty())
{
sData.push(sTmp.top());
sTmp.pop();
}
// 这里就不能用swap了
//sTmp.swap(sData);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
int n = sData.top();
sData.pop();
return n;
}
/** Get the front element. */
int peek() {
return sData.top();
}
/** Returns whether the queue is empty. */
bool empty() {
return sData.empty();
}
private:
std::stack<int> sData;
};
LeetCode 155 - Min Stack - 最小栈 - easy
设计一个支持 push ,pop ,top 操作,并能在常数时间(时间复杂度O(1))内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
示例:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
前两道题时间复杂度都是O(n)。 思路是用空间换时间,但是只4增加一个私有变量nMin是无法记录所有状态的,比如pop之后,,,
一个变量不行,就增加多个变量:最小值栈,top记录当前状态的最小值。
需要注意,访问容器一定要判断是否为空。
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
sData.push(x);
if(sMin.empty())
{
sMin.push(x);
}
else
{
sMin.push( x < sMin.top() ? x : sMin.top());
}
}
void pop() {
sData.pop();
sMin.pop();
}
int top() {
return sData.top();
}
int getMin() {
return sMin.top();
}
private:
std::stack<int> sData;
// 最小值栈,top记录当前状态的最小值。
std::stack<int> sMin;
};
Leetcode 946 - Validate Stack Sequences - 验证栈序列 - Medium
类似的还有poj平台1363 Rails。
给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
思路是,借助栈存储pushed序列,一段判断pushed.top() == poped.front()
,匹配则均pop。如果最后栈空了,则返回true。
这个复杂度为O(n),不是n方。
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> s;
int nSize = pushed.size();
int j = 0;
for (int i = 0; i < nSize; ++i)
{
s.push(pushed[i]);
while(!s.empty() && s.top() == popped[j])
{
s.pop();
++j;
}
}
return s.empty() ? true : false;
}
};
LeetCode 224 - Basic Calculator - Hard
实现一个基本的计算器来计算一个简单的字符串表达式的值。
字符串表达式可以包含左括号 ( ,右括号 ),加号 + ,减号 -,非负整数和空格 。
示例 :
输入: "(1+(4+5+2)-3)+(6+8)"
输出: 23
说明:
你可以假设所给定的表达式都是有效的。
请不要使用内置的库函数 eval。
需要基于数字栈和符号栈,实现逆波兰式,也叫后缀表达式。
设计状态机:
框架:
for (int i = 0; i < nLen; ++i)
{
if ( s[i] == ' ' ) continue;
switch(state)
{
case STATE_BEGIN:
// judge STATE
--i
break;
case STATE_NUM:
// if num, number = ...
// else, compute, then STATE_OP
break;
case STATE_OP:
// if +-, push, then set bCalc
// if (, STATE_NUM, unset bCalc
// if ), compute
// if num, STATE_NUM
break;
}
}
if (nRes != 0)
{
stackNum.push(nRes);
compute(stackNum, stackOp);
}
else if( nRes == 0 && stackNum.empty())
{
return 0;
}
完整版:
class Solution {
public:
int calculate(string s) {
// 测试中有大于(2**31-1的输入,所以用long
std::stack<long> stackNum;
std::stack<char> stackOp;
long nNum = 0;
enum State state = STATE_BEGIN;
bool bCalc = false;
int nLen = s.length();
for (int i = 0; i < nLen; ++i)
{
if (s[i] == ' ') continue;
switch (state)
{
case STATE_BEGIN:
// judge STATE
state = (s[i] >= '0' && s[i] <= '9')
? STATE_NUM
: STATE_OP;
--i;
break;
case STATE_NUM:
// if num, number = ...
// else, compute, then STATE_OP
if (s[i] >= '0' && s[i] <= '9')
{
nNum = nNum * 10 + s[i] - '0';
}
else
{
stackNum.push(nNum);
if (bCalc)
{
compute(stackNum, stackOp);
}
nNum = 0;
--i;
state = STATE_OP;
}
break;
case STATE_OP:
// if +-, push, then set bCalc
// if (, STATE_NUM, unset bCalc
// if ), compute
// if num, STATE_NUM
if (s[i] == '+' || s[i] == '-')
{
stackOp.push(s[i]);
bCalc = true;
}
else if (s[i] == '(')
{
state = STATE_NUM;
bCalc = false;
}
else if (s[i] == ')')
{
compute(stackNum, stackOp);
}
else if (s[i] >= '0' && s[i] <= '9')
{
state = STATE_NUM;
--i;
}
break;
}
}
if (nNum != 0)
{
stackNum.push(nNum);
compute(stackNum, stackOp);
}
else if (nNum == 0 && stackNum.empty())
{
return 0;
}
return stackNum.top();
}
private:
void compute(std::stack<long> &stackNum, std::stack<char> &stackOp)
{
if (stackNum.size() < 2) return;
long nNum1 = stackNum.top();
stackNum.pop();
long nNum2 = stackNum.top();
stackNum.pop();
char cOp = stackOp.top();
if (cOp == '+')
{
stackNum.push(nNum2 + nNum1);
}
else if (cOp == '-')
{
stackNum.push(nNum2 - nNum1);
}
stackOp.pop();
}
private:
enum State {
STATE_BEGIN = 0,
STATE_NUM = 1,
STATE_OP = 2
};
};
LeetCode 215 - Kth Largest Element in an Array - 数组中第k大的数 - easy
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
二叉堆
最大/小二叉堆:最大/小值先出的完全二叉树。
最大堆:
最小堆:
堆又叫优先级队列,对应std::priority_queue
容器。
int main()
{
std::vector<int> v({ 6, 10, 1, 7, 99, 4, 33 });
// 默认最大堆
std::priority_queue<int> big_heap(v.begin(), v.end());
// 最小堆
std::priority_queue<int, std::vector<int>, std::greater<int>> small_heap(v.begin(), v.end());
// 最大堆
std::priority_queue<int, std::vector<int>, std::greater<int>> big_heap_1;
// 99
std::cout << big_heap.top() << std::endl;
// 1
std::cout << small_heap.top() << std::endl;
big_heap.push(1000);
// 1000
std::cout << big_heap.top() << std::endl;
big_heap.pop();
big_heap.pop();
big_heap.pop();
// 10
std::cout << big_heap.top() << std::endl;
return 0;
}
解
要想让top为第k大的数,那么二叉树其余元素为大于堆顶的k-1个数,也就是需要借助一个元素个数为k的最小堆。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
std::priority_queue<int, std::vector<int>, std::greater<int>>
small_heap;
int nSize = nums.size();
for(int i = 0; i < nSize; ++i)
{
if (small_heap.size() < k)
{
small_heap.push(nums[i]);
}
else if ( nums[i] > small_heap.top())
{
small_heap.pop();
small_heap.push(nums[i]);
}
}
return small_heap.top();
}
};
LeetCode 295 - Find Median from Data Stream - 寻找中位数 - Hard
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
进阶:
- 如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
- 如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?
思路
最笨的方法,每次添加后都对数组排序,这样addNum为O(n),findMedian为O(1);若查找中位数时排序,则addNum为O(1),findMedian为O(nlogn)。
因为两种操作都是随机的,则n次操作最理想的复杂度为n方。
思路是,用最大堆和最小堆各存一半数据,保持最大堆top比最小堆top小。
搞清楚,最小堆存储大的那一半数据。
添加元素3种情况。
当两个堆size相同时:
- 新元素小于两个top,则添加到小的那半数据里(最大堆);
- 新元素大于两个top,则添加到大的那半数据里(最小堆)。
最大堆比最小堆多一个元素:
- 新元素大于最大堆top,直接push进较少的最小堆里;
- 新元素小于最大堆top,则肯定要push进最大堆,但首先要把top加进最小堆。
最大堆比最小堆少一个元素,类比上一种情况即可。
代码
class MedianFinder {
public:
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
if (big_heap.empty())
{
big_heap.push(num);
return;
}
int nBigHeap = big_heap.size();
int nSmallHeap = small_heap.size();
if (nBigHeap == nSmallHeap)
{
if (num < big_heap.top())
{
big_heap.push(num);
}
else
{
small_heap.push(num);
}
}
else if (nBigHeap > nSmallHeap)
{
if (num > big_heap.top())
{
small_heap.push(num);
}
else
{
small_heap.push(big_heap.top());
big_heap.pop();
big_heap.push(num);
}
}
else if (nBigHeap < nSmallHeap)
{
if (num < small_heap.top())
{
big_heap.push(num);
}
else
{
big_heap.push(small_heap.top());
small_heap.pop();
small_heap.push(num);
}
}
}
double findMedian() {
if (big_heap.size() == small_heap.size())
{
return (big_heap.top() + small_heap.top()) / 2.0;
}
return (big_heap.size() > small_heap.size())
? big_heap.top()
: small_heap.top();
}
private:
// 用最大堆和最小堆各存一半数据
// 保持最大堆top比最小堆top小
std::priority_queue<int> big_heap;
std::priority_queue<int, std::vector<int>, std::greater<int>> small_heap;
};