Leetcode复盘3——栈和队列
导读
这是我写的第二篇复盘总结,总结了10道链表相关的题目,里面包含了所有链表的基本操作,包括:
- 如何定义一个指针(C++ ListNode *pA = head, Java ListNode pA = head)
- 如何反转链表;(设置一个临时指针temp,先记录cur的下一个,反转,pre和cur各往前走一步)
- 如何区分指针移动和指向:当为指向时“.next”在等号左边,即用当前指针指向等号右边的节点;当为移动时”.next”在等号右边,即把某一个指针指向的节点赋值给等号左边
1.用栈实现队列(Leetcode232)
难度:简单Easy
idea: 双栈(two Stack)
由栈的特点,即先入后出,队列里面的值先进入第一个栈(in栈)后顺序被颠倒过来了,从in栈进到第二个栈(out栈)顺序又被反过来了,这样就实现了先入先出的方法
代码:
Java版
class MyQueue {
// 在所有函数外定义两个栈,第一个函数体中赋值
private Stack<Integer> in = new Stack<>();
private Stack<Integer> out = new Stack<>();
/** Initialize your data structure here. */
public MyQueue() {
in = new Stack<>(); // 第一个栈(in栈)
out = new Stack<>(); // 第二个栈(out栈)
}
/** Push element x to the back of queue. */
public void push(int x) { // 只是把元素加到"队列"里(后入后出)
in.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() { // 当从"队列"中弹出元素时要注意了,一定是从out栈里弹,里面会涉及到一个从in栈到out栈转移的过程
in2out();
return out.pop();
}
/** Get the front element. */
public int peek() { // 当看一眼"队列"顶的元素注意了,一定是从out栈里看
in2out();
return out.peek();
}
/*单独定义一个从in栈到out栈转移的过程,每次转移的过程需要保证out栈是空的*/
private void in2out() { // 不传参数且不返回任何东西
if(out.isEmpty()) {
while(!in.isEmpty()) { // 调用1次in2out函数,把in中的元素全部都转移到out里
out.push(in.pop());
}
}
}
/** Returns whether the queue is empty. */
public boolean empty() { // 保证in和out都为空即可
return in.isEmpty() && out.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
2.用队列实现栈(Leetcode225)
难度:简单Easy
idea
本题跟上一题用栈实现队列不同,此处只需要一个队列即可,方法是当push进队列尾部一个元素时,要让其先出,即把其他的元素依次pop出去,在push进队列尾部,定义3个函数:
1.push函数: 把一个元素push进队列尾部(必须),再把之前其他的元素pop出去再push进队列(非必须)
2.pop函数: 把除队尾的其他元素依次pop出去再push进队列(必须),再把队尾的元素pop出去(必须)
3.peek函数: 把除队尾的其他元素依次pop出去再push进队列(必须),再看一眼队尾的元素(必须)
综上可知,2和3都需要把之前其他的元素先pop出去再push进来,故可以把这一步放在push函数里
class MyStack {
queue<int> queue; // 函数外定义+赋值(queue貌似不是C++中的关键字)
public:
/** Initialize your data structure here. */
MyStack() {
//nothing to do
}
/** Push element x onto stack. */
void push(int x) {
queue.push(x);
// 把除了队尾的元素都pop出去再push进来
for(int i = 0; i < queue.size() - 1; i++) {
queue.push(queue.front());
queue.pop();
}
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int num = queue.front(); // C++好奇怪啊,pop出去的不能直接用
queue.pop();
return num;
}
/** Get the top element. */
int top() {
return queue.front(); // push函数都调整好了,此时队首的元素即刚加进队尾的元素
}
/** Returns whether the stack is empty. */
bool empty() {
return queue.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
Java版
class MyStack {
private Queue<Integer> queue; // 所有函数外定义
/** Initialize your data structure here. */
public MyStack() {
queue = new LinkedList<>(); // 函数内赋值
}
/** Push element x onto stack. */
public void push(int x) {
queue.add(x);
// 因为pop和peek都需要把除队尾之外的其他元素pop出去再push进队尾,故放在这里即可
int cnt = queue.size();
while (cnt-- > 1) {
queue.add(queue.poll());
}
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return queue.remove(); // 之前都弄好了,队首的元素即刚加进来的元素
}
/** Get the top element. */
public int top() {
return queue.peek(); // 之前都弄好了,队首的元素即刚加进来的元素
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue.isEmpty(); // 之前都弄好了,队首的元素即刚加进来的元素
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
3.最小栈 / 最小值栈(LeetCode155)
难度:简单Easy
idea: 双栈法
定义两个栈Stack和minStack,其中Stack为一般的栈,而minStack栈只用来保存当前最小值,即栈顶元素
当有一个新的元素要入栈时,直接加入Stack中,再判断它与minStack栈顶元素的大小,若小于栈顶元素,则也把它加到minStack里;同理当要删除一个元素时,直接从Stack栈中删除,当要删除的元素和minStack栈顶的元素相等时,再把minStack栈顶元素删除
代码:
Java版
class MinStack {
/** initialize your data structure here. */
private Stack<Integer> stack; // 函数外定义
private Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>(); // 函数内赋值
minStack = new Stack<>();
}
public void push(int x) {
stack.push(x); // stack直接加入
if(!minStack.isEmpty()) {
int top = minStack.peek();
if(x <= top) minStack.push(x);
} else {
minStack.push(x); // minStack为空的,直接把x加进去即可
}
}
public void pop() {
int pop = stack.pop();
int top = minStack.peek();
// 当pop和top相等时minStack再出栈
if(pop == top) {
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek(); // minStack保存的是所有元素最小值
}
}
/**
* 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.getMin();
*/
Python版
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = []
def push(self, x: int) -> None: # 当有一个新元素加入时
self.stack.append((x, min(self.getMin(), x))) # 第[0]维直接加入,第[1]维比较它和之前最小值的大小
def pop(self) -> None:
self.stack.pop()
def top(self) -> int: # 看一眼栈顶[-1]的元素[0]
if self.stack:
return self.stack[-1][0]
def getMin(self) -> int: # 得到当前所有元素[-1]的最小值[1]
if self.stack: # 若栈不为空
return self.stack[-1][1]
return float("inf") # 若栈为空,最小值为正无穷
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
4.有效的括号 / 用栈实现括号匹配(LeetCode20)
难度:简单Easy
idea: 栈(Stack)
先把字符串型转换成字符串数组,用Java里toCharArray()方法,这样就可以用for循环一个个去取了
代码:
Java版
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>(); // 定义栈stack并赋值
for(char c : s.toCharArray()) {
if(c == '(' || c == '[' || c == '{') { // 当为左括号时入栈
stack.push(c);
} else { // 此时为右括号,需要从stack弹出左括号来匹配
if(stack.isEmpty()) return false;
char cStack = stack.pop(); // 从stack里弹出左括号去跟c这个右括号匹配
boolean b1 = c == ')' && cStack != '('; // 当c为右小括号并且弹出的不为左小括号时置true
boolean b2 = c == ']' && cStack != '[';
boolean b3 = c == '}' && cStack != '{';
if (b1 || b2 || b3) { // 当b1或b2或b3有一个为true时就返回失败
return false;
}
}
}
return stack.isEmpty();
}
}
5.每日温度 / 数组中元素与下一个比它大的元素之间的距离(LeetCode739)
idea: 单调栈
本地的目的是找到右边第一个比它大的元素,并计算之间的距离,故考虑单调栈,从栈底到栈顶元素依次递减,当来一个新元素时,若比当前栈顶的元素大,则代表找到了栈顶元素那个值离它最近的较大值,返回坐标之差.
具体方法为生成一个和给定数组长度一样的数组(单调栈),用来存储各个维度对应的下标(因为本题要返回距离,故用下标计算),当当前元素大于栈顶元素时,证明栈顶元素minStack[-1]找到了离它最近的较大值,放到结果列表对应的位置,即res[minStack[-1]]
1.制作返回列表和最小栈
2.for循环遍历给定列表的每个元素,
3.while循环比较当前元素和栈顶元素的大小,
4.当当前元素大于栈顶元素时,计算在返回列表中的位置,并弹出栈顶元素
5.跳出while循环后,把当前元素放到它应该待的位置,压入栈
代码
Java版
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length; // 记录数组的长度
int[] dist = new int[n]; // 生成一个和数组大小一样的数组,用来输出各个距离
Stack<Integer> indexs = new Stack<>(); // 定义单调栈,从栈底到栈顶元素依次递减
for (int curIndex = 0; curIndex < n; curIndex++) {
// while循环,当当前元素值(下标curIndex)大于栈顶元素(下标preIndex)时,就知道preIndex和curIndex距离之差了,
// 若当前元素依旧大于栈顶元素,那么新的栈顶元素(preIndex)与当前元素的距离之差也知道了,依次类推.
while (!indexs.isEmpty() && temperatures[curIndex] > temperatures[indexs.peek()]) {
int preIndex = indexs.pop(); // 当前元素大于栈顶元素,pop出栈顶元素,开始求距离
dist[preIndex] = curIndex - preIndex;
}
indexs.add(curIndex); // 找到当前元素应该待的位置了,压入栈
}
return dist;
}
}
Python版
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
length = len(T)
res = [0] * length # 生成一个和元素组一样长度的数组,用来记录每个温度离它最近的较大值之间的距离
minStack = []
for i in range(length):
while(minStack and T[minStack[-1]] < T[i]): # 若当前元素T[i]大于栈顶元素时,则可以给栈顶元素安排了
res[minStack[-1]] = i - minStack[-1]
minStack.pop()
minStack.append(i)
return res
6.下一个更大元素 II / 循环数组中比当前元素大的下一个元素(LeetCode503)
idea: 单调栈(stack)
把原始数组想象成一个个人,元素的大小为身高,环形数组即把原始数组"翻倍",就是在后面再接一个原始数组,这样的话,按照之前"比身高"的流程,每个元素不仅可以比较自己右边的元素,而且也可以和左边的元素比较了.
此题采用从后往前遍历的方法,即"即时性"方法:
如果当前元素大于栈顶元素,则一口气把小的全都pop出去,将当前元素当做定海神针;
如果当前元素小于栈顶元素,则直接计算当前元素在res中的位置(本题用res返回结果,这是一般情况,上一题用字典返回属于特殊情况)
代码:
C++版
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> res(n); // 存放结果
stack<int> stack; // 单调栈
// 假装这个数组长度翻倍了
for (int i = 2 * n - 1; i >= 0; i--) { // 从后往前入栈
while (!stack.empty() && stack.top() <= nums[i % n]) // 当当前的值比栈顶的元素大时,证明来了新的占山之宝,把旧的pop出栈
stack.pop();
res[i % n] = stack.empty() ? -1 : stack.top(); // 否则新的数都以旧的栈顶元素计算离其最近的更大值
stack.push(nums[i % n]);
}
return res;
}
};
Python版
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
nums = nums * 2 # 原数组扩大一倍
stack = []
res = [-1] * len(nums)
for idx, num in enumerate(nums):
while stack and nums[stack[-1]] < num: # 当前元素大于栈顶元素了,依次计算栈顶元素
res[stack.pop()] = num # 分两步:1.pop出去; 2.计算在res中的位置
stack.append(idx) # 不管是从左往右的"滞后性"还是从右往左的"即时性"最后都要把当前值压入栈
return res[:len(nums) // 2] # 只返回res前半部分的值