这里写目录标题
一、栈(Stack)
20-有效的括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([)]"
输出:false
示例 5:
输入:s = "{[]}"
输出:true
提示:
1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成
class Solution {
public:
bool isValid(string s) {
stack<char> stk;
for(char c : s){
if(c == '(' || c == '[' || c == '{'){
stk.push(c);
}else{
if(stk.size() == 0){
return false;
}
if(c == ')' && stk.top() != '('){
return false;
}
if(c == ']' && stk.top() != '['){
return false;
}
if(c == '}' && stk.top() != '{'){
return false;
}
stk.pop();
}
}
return stk.size() == 0;
}
};
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for c in s:
if c == '(' or c == '[' or c == '{':
stack.append(c)
else:
if len(stack) == 0:
return False
if c == ")" and stack.pop() != "(":
return False
if c == "]" and stack.pop() != "[":
return False
if c == "}" and stack.pop() != "{":
return False
if stack != []:
return False
return True
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for i in range(len(s)):
if s[i] == "{" or s[i] == "[" or s[i] == "(":
stack.append(s[i])
else:
if len(stack) == 0:
return False
elif s[i]=="}" and stack.pop()!="{":
return False
elif s[i]=="]" and stack.pop()!="[":
return False
elif s[i]==")" and stack.pop()!="(":
return False
return len(stack) == 0
1190-反转每对括号间的子串
给出一个字符串 s(仅含有小写英文字母和括号)。
请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。
注意,您的结果中 不应 包含任何括号。
示例 1:
输入:s = "(abcd)"
输出:"dcba"
示例 2:
输入:s = "(u(love)i)"
输出:"iloveu"
示例 3:
输入:s = "(ed(et(oc))el)"
输出:"leetcode"
示例 4:
输入:s = "a(bcdefghijkl(mno)p)q"
输出:"apmnolkjihgfedcbq"
提示:
- 0 <= s.length <= 2000
- s 中只有小写英文字母和括号
- 我们确保所有括号都是成对出现的
栈是一种先进后出的数据结构.
-
只要不是右括号就直接入栈
-
遇到了右括号
- while循环 抛出栈中 遇到(之前 的字符, append 到 tmp数组中. 相当于把字符串掉了个儿
- 将栈中的左括号抛出
-
继续进行, 将所有s的字符都遍历完
-
最后栈中剩下的就是最终的字符串结果, 将栈转成字符串str
class Solution:
def reverseParentheses(self, s: str) -> str:
stack = []
for c in s:
if c != ")": # 只要不是右括号就直接入栈
stack.append(c)
else: # 遇到了右括号
temp = []
while stack and stack[-1] != "(": # while循环 抛出栈中 遇到(之前 的字符, append 到 tmp数组中. 相当于把字符串翻转
temp.append(stack.pop())
stack.pop() # 将栈中的左括号抛出
stack += temp # 将翻转后的字符串重新串联到stack中
return "".join(stack)
solution = Solution()
s = "(ed(et(oc))el)"
result = solution.reverseParentheses(s)
print("result = ", result)
class Solution {
public:
string reverseParentheses(string s) {
string stk;
for(char ch : s) {
if(ch != ')') { // 只要不是右括号就直接入栈
stk.push_back(ch);
} else { // 遇到了右括号
string temp;
while(stk != "" && stk.back() != '('){ // while循环 抛出栈中 遇到(之前 的字符, append 到 tmp数组中. 相当于把字符串翻转
temp.push_back(stk.back());
stk.pop_back();
}
stk.pop_back(); // 将栈中的左括号抛出
stk = stk + temp; // 将翻转后的字符串重新串联到stk中
}
}
return stk;
}
};
class Solution {
public:
string reverseParentheses(string s) {
stack<string> stk;
string str;
for(auto& ch : s) {
if(ch == '(') {
stk.push(str);
str = "";
} else if (ch == ')') {
reverse(str.begin(), str.end());
str = stk.top() + str;
stk.pop();
} else{
str += ch;
}
}
return str;
}
};
150. 逆波兰表达式求值
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
提示:
- 1 <= tokens.length <= 104
- tokens[i] 是一个算符(“+”、“-”、“*” 或 “/”),或是在范围 [-200, 200] 内的一个整数
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
- 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
- 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
- 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if not token in '+-*/':
stack.append(int(token))
else:
if token == '+':
stack.append(stack.pop() + stack.pop())
elif token == '-':
stack.append(0 - stack.pop() + stack.pop())
elif token == '*':
stack.append(stack.pop() * stack.pop())
else:
a = stack.pop()
b = stack.pop()
stack.append(int(b / a))
return sum(stack)
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> stk;
int n = tokens.size();
for (int i = 0; i < n; i++) {
std::cout << tokens[i] << std::endl;
string token = tokens[i];
if (isNumber(token)) {
stk.push(std::stoi(token)); // C++:将string转为int型
} else {
int num2 = stk.top();
stk.pop();
int num1 = stk.top();
stk.pop();
switch (token[0]) {
case '+':
stk.push(num1 + num2);
break;
case '-':
stk.push(num1 - num2);
break;
case '*':
stk.push(num1 * num2);
break;
case '/':
stk.push(num1 / num2);
break;
}
}
}
return stk.top();
}
bool isNumber(string& token) {
return !(token == "+" || token == "-" || token == "*" || token == "/");
}
};
官解C++版本运行下面的测试用例 [“-128”,“-128”,““,”-128","”,“-128”,““,“8”,””,“-1”,“*”] 是计算结果会溢出int的范围,应该改成long类型
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long> stk;
int n = tokens.size();
for (int i = 0; i < n; i++) {
std::cout << tokens[i] << std::endl;
string token = tokens[i];
if (isNumber(token)) {
stk.push(std::stol(token)); // C++:将string转为int型
} else {
long num2 = stk.top();
stk.pop();
long num1 = stk.top();
stk.pop();
switch (token[0]) {
case '+':
stk.push(num1 + num2);
break;
case '-':
stk.push(num1 - num2);
break;
case '*':
stk.push(num1 * num2);
break;
case '/':
stk.push(num1 / num2);
break;
}
}
}
return stk.top();
}
bool isNumber(string& token) {
return !(token == "+" || token == "-" || token == "*" || token == "/");
}
};
232-用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
- void push(int x) 将元素 x 推到队列的末尾
- int pop() 从队列的开头移除并返回元素
- int peek() 返回队列开头的元素
- boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
- 你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
- 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
进阶:你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。
示例:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
- 1 <= x <= 9
- 最多调用 100 次 push、pop、peek 和 empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)
具体操作过程如下面的动图所示:
class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
"""
self.stack01 = []
self.stack02 = []
def push(self, x: int) -> None:
"""
Push element x to the back of queue.
"""
self.stack01.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
if not self.stack02:
while self.stack01:
self.stack02.append(self.stack01.pop())
return self.stack02.pop()
def peek(self) -> int:
"""
Get the front element.
"""
if not self.stack02:
while self.stack01:
self.stack02.append(self.stack01.pop())
return self.stack02[-1]
def empty(self) -> bool:
"""
Returns whether the queue is empty.
"""
return len(self.stack01) == 0 and len(self.stack02) == 0
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
剑指 Offer 09-用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
提示:
- 1 <= values <= 10000
- 最多会对 appendTail、deleteHead 进行 10000 次调用
栈1用于存储新元素,栈2用于弹出旧元素
- appendTail:直接将新元素压入栈1中
- deleteHead:直接弹出栈2中的元素;如果栈2为空,则先将栈1中的所有的元素压入栈2中,然后再弹出栈2中的元素;如果栈1也为空,返回 -1
class CQueue {
public:
stack<int> stk;
stack<int> temp;
CQueue() {
}
void appendTail(int value) {
stk.push(value);
}
int deleteHead() {
if(temp.empty()){
while(!stk.empty()){
temp.push(stk.top());
stk.pop();
}
}
if(!temp.empty()){
int result = temp.top();
temp.pop();
return result;
}else{
return -1;
}
}
};
/**
* Your CQueue object will be instantiated and called as such:
* CQueue* obj = new CQueue();
* obj->appendTail(value);
* int param_2 = obj->deleteHead();
*/
class CQueue:
def __init__(self):
self.stack = []
self.temp = []
def appendTail(self, value: int) -> None:
self.stack.append(value)
def deleteHead(self) -> int:
if not self.temp:
while self.stack:
self.temp.append(self.stack.pop())
return self.temp.pop() if self.temp else -1
# Your CQueue object will be instantiated and called as such:
# obj = CQueue()
# obj.appendTail(value)
# param_2 = obj.deleteHead()
394-字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例 3:
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
示例 4:
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
class Solution:
def decodeString(self, s: str) -> str:
stack = []
result = ""
multi = 0
for c in s:
if '0' <= c <= '9':
multi = multi * 10 + int(c) # 当前的multi【实现多位数 125】
elif c == '[':
stack.append([result, multi]) # result:上一次的result;multi:当前的multi
result = ""
multi = 0 # multi置零
elif c != ']': # [] 之间的内容
result += c
else: # c == ']',当遇到“]”时,拼接之前的字符串
last_result, cur_multi = stack.pop()
result = last_result + cur_multi * result
return result
solution = Solution()
s = "3[acb2[gh]]"
result = solution.decodeString(s)
print("result = ", result)
class Solution {
public:
string decodeString(string s) {
stack<std::tuple<string, int>> stk;
string result;
int multi = 0;
for(char c : s){
if('0' <= c && c <= '9'){
multi = multi * 10 + (c - '0'); // 当前的multi【实现多位数 125】
}else if(c == '['){
tuple<string, int> tup(result, multi); // result:上一次的result; multi:当前的multi
stk.push(tup);
result = ""; // result置空
multi = 0; // multi置零
}else if(c != ']'){ // [] 之间的内容
result += c;
}else{ // c == ']',当遇到“]”时,拼接之前的字符串
string lastResult;
int curMulti = 0;
tie(lastResult, curMulti) = stk.top();
stk.pop();
string str;
str = str + lastResult;
for(int i = 0; i < curMulti; i++){
str += result;
}
result = str;
}
}
return result;
}
};
1047-删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
提示:
- 1 <= S.length <= 20000
- S 仅由小写英文字母组成。
class Solution:
def removeDuplicates(self, s: str) -> str:
stack = []
for c in s:
if stack == []:
stack.append(c)
elif c == stack[-1]:
stack.pop()
else:
stack.append(c)
return "".join(stack)
class Solution:
def removeDuplicates(self, s: str) -> str:
stack = []
for c in s:
if stack != [] and c == stack[-1]:
stack.pop()
else:
stack.append(c)
return "".join(stack)
class Solution {
public:
string removeDuplicates(string S) {
stack<char> st;
for (char s : S) {
if (st.empty() || s != st.top()) {
st.push(s);
} else {
st.pop(); // s 与 st.top()相等的情况
}
}
string result = "";
while (!st.empty()) { // 将栈中元素放到result字符串汇总
result += st.top();
st.pop();
}
reverse(result.begin(), result.end()); // 此时字符串需要反转一下
return result;
}
};
拿字符串直接作为栈,这样省去了栈还要转为字符串的操作
// C++
class Solution {
public:
string removeDuplicates(string s) {
string stk;
for (char ch : s) {
if (!stk.empty() && stk.back() == ch) {
stk.pop_back();
} else {
stk.push_back(ch);
}
}
return stk;
}
};
155-最小栈 【剑指 Offer 30. 包含min函数的栈】
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
- MinStack() 初始化堆栈对象。
- void push(int val) 将元素val推入堆栈。
- void pop() 删除堆栈顶部的元素。
- int top() 获取堆栈顶部的元素。
- int 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.
提示:
- pop、top 和 getMin 操作总是在 非空栈 上调用。
- − 2 31 < = v a l < = 2 31 − 1 -2^{31} <= val <= 2^{31} - 1 −231<=val<=231−1
- pop、top 和 getMin 操作总是在 非空栈 上调用
- push, pop, top, and getMin最多被调用 3 ∗ 1 0 4 3 * 10^4 3∗104 次
根据题意,我们需要在常量级的时间内找到最小值!
这说明,我们绝不能在需要最小值的时候,再做排序,查找等操作来获取!
所以,我们可以创建两个栈,一个栈是主栈 stackstack,另一个是辅助栈 minStackminStack,用于存放对应主栈不同时期的最小值。
class MinStack {
public:
stack<int> stk;
stack<int> min_stk;
MinStack() {
min_stk.push(INT_MAX); // 初始化min_stk
}
void push(int val) {
stk.push(val);
min_stk.push(min(min_stk.top(), val));
}
void pop() {
stk.pop();
min_stk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return min_stk.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = []
self.min_stack = [float('inf')]
def push(self, val: int) -> None:
self.stack.append(val)
self.min_stack.append(min(val, self.min_stack[-1]))
def pop(self) -> None:
self.stack.pop()
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(val)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
227. 基本计算器 II
给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
示例 1:
输入:s = "3+2*2"
输出:7
示例 2:
输入:s = " 3/2 "
输出:1
示例 3:
输入:s = " 3+5 / 2 "
输出:5
提示:
- 1 <= s.length <= 3 ∗ 1 0 5 3 * 10^5 3∗105
- s 由整数和算符 (‘+’, ‘-’, ‘*’, ‘/’) 组成,中间由一些空格隔开
- s 表示一个 有效表达式
- 表达式中的所有整数都是非负整数,且在范围 [ 0 , 2 31 − 1 ] [0, 2^{31} - 1] [0,231−1] 内
- 题目数据保证答案是一个 32-bit 整数
题目要求:实现一个基本计算器来计算并返回它的值。
其中,整数除法仅保留整数部分(在 Python 中,这里有个小坑,后面再提及,且继续往后看)。
再看题目最后的提示,其中第 2 条说明:
- 字符串表达式 \rm{s}s 由整数和算符(‘+’, ‘-’, ‘*’, ‘/’) 组成,中间由一些空格隔开
即是说,表达式不会出现括号,所以乘除的优先级高于加减运算。也就是说,我们可以考虑先将表达式中乘除部分先行计算出来放回表达式中,那么剩余的就是加减运算。
在这道题中,出现运算符 (‘*’, ‘/’) 时,其与左右两边数字组成部分即是要先计算结果的部分。
以 ‘a * b’ 为例,也就说当 ‘*’ 出现后,紧接后面的 b 即可与 a 进行乘法运算保留运算后的结果,‘/’ 同理。
在这里,我们选用栈来存储数值。要判断表达式部分是否需要先行计算取决于整数之间运算符,这里用 pre_sign 表示。
具体的策略如下:
- 声明栈 \rm{stack}stack,遍历字符串;
- 若字符是数字,将其转换为整型数值;
- 若字符为运算符,那么根据 pre_sign 来决定后面的运算:
- 当 pre_sign 为 ‘+’ 号时,直接将紧跟的数值压入栈中;
- 当 pre_sign 为 - 号时,将紧跟数值取反后压入栈中;
- 当 pre_sign 为 * 或 / 号时,弹出栈顶元素,与运算符紧跟后面的数值进行计算后,再将结果压入栈中。
题目提示第 4 条说明,表达式中所有的整数都是非负整数。结合第 3 条提示,当遍历字符串表达式时,第一个遇到的整数前面不会出现任何运算符,那么这里将 pre_sign 初始化为 ‘+’ 号。
前面的策略中,是当遇到下一个运算符时,才根据 pre_sign 处理前面的运算。当到达末尾时,表达式最后是不会出现运算符的,所以到达末尾也需要根据 pre_sign 处理前面的运算,防止出现错误。
前面开篇所说的小坑,这里说明下,题目要求除法保留整数部分。但Python 当中 // 运算结果时向下取整,如果是负数的话,那么这里的计算就会出现偏差。例如,下面的测试用例 (未通过):
输入:
"14-3/2"
输出:
12
预期:
13
当使用 // 运算时,输出的结果时 12,但正确答案应该是 13。出现的错误的原因就是因为 -3 / 2 这部分,直接用 // 取整,-3 // 2 的结果是 -2,而非是预期的 -1。所以考虑使用 / 运算,然后对结果进行取整。
这里以示例 1,用动图的形式展示运算的过程:
class Solution {
public:
int calculate(string s) {
vector<int> stk;
char prev_op = '+';
int num = 0;
for(int i = 0; i < s.size(); i++){
if(isdigit(s[i])){
num = num * 10 + int(s[i] - '0');
}
string ops = "+-*/";
if(ops.find(s[i]) != string::npos || i == s.size() - 1){
std::cout << "s[i] = " << s[i] << std::endl;
if(prev_op == '+'){
stk.push_back(num);
}
if(prev_op == '-'){
stk.push_back(0 - num);
}
if(prev_op == '*'){
stk.back() = stk.back() * num;
}
if(prev_op == '/'){
stk.back() = stk.back() / num;
}
num = 0;
prev_op = s[i];
}
}
return accumulate(stk.begin(), stk.end(), 0);
}
};
class Solution:
def calculate(self, s: str) -> int:
stack = []
prev_op = "+" # 保存当前num前面的运算符
num = 0 # 保存运算符号之间的数值
for idx, c in enumerate(s):
# 获取运算符号之间的数值
if '0' <= c <= '9':
num = num * 10 + int(c)
# 注意 idx == len(s) - 1 这个条件,这里表示到达末尾
# 这里是根据 prev_op 来确定其跟在后面的数字的计算方式
# 代码中,遇到"下一个"运算符时,才会处理前面的运算,本题当到达末尾时,不会再出现单独的运算符,所以需要进行额外判断,到达末尾同样处理前面的运算
if c in '+-*/' or idx == len(s) - 1:
if prev_op == '+':
stack.append(num)
if prev_op == '-':
stack.append(-num)
if prev_op == '*':
stack.append(stack.pop() * num)
if prev_op == '/':
stack.append(int(stack.pop() / num))
num = 0
prev_op = c
return sum(stack)
剑指 Offer 31. 栈的压入、弹出序列【946. 验证栈序列】
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 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 之前弹出。
提示:
- 0 <= pushed.length == popped.length <= 1000
- 0 <= pushed[i], popped[i] < 1000
- pushed 是 popped 的排列。
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> stk;
int idx = 0;
for(int num : pushed){
stk.push(num);
while(!stk.empty() && stk.top() == popped[idx]){
stk.pop();
idx += 1;
}
}
return stk.size() == 0;
}
};
class Solution:
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
stack = []
idx = 0
for num in pushed:
stack.append(num)
while stack and stack[-1] == popped[idx]:
stack.pop()
idx += 1
return len(stack) == 0
面试题 01.06. 字符串压缩
字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。若“压缩”后的字符串没有变短,则返回原先的字符串。你可以假设字符串中只包含大小写英文字母(a至z)。
示例1:
输入:"aabcccccaaa"
输出:"a2b1c5a3"
示例2:
输入:"abbccd"
输出:"abbccd"
解释:"abbccd"压缩后为"a1b2c2d1",比原字符串长度更长。
提示:
- 字符串长度在[0, 50000]范围内。
class Solution {
public:
string compressString(string S) {
string result;
int num = 0;
for(char c : S){
if(result.size() == 0){
result += c;
num++;
}else{
if(c == result.back()){
num++;
}else{
result += to_string(num);
result += c;
num = 1;
}
}
}
result += to_string(num);
return result.size() < S.size() ? result : S;
}
};
class Solution:
def compressString(self, S: str) -> str:
stack = []
num = 0
for c in S:
if not stack:
stack.append(c)
num += 1
else:
if c == stack[-1]:
num += 1
else:
stack.append(str(num))
stack.append(c)
num = 1
stack.append(str(num))
if len(stack) < len(S):
return ''.join(stack)
else:
return S
单调栈
402-移掉K位数字
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意:
- num 的长度小于 10002 且 ≥ k。
- num 不会包含任何前导零。
示例 1 :
输入: num = "1432219", k = 3
输出: "1219"
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
示例 2 :
输入: num = "10200", k = 1
输出: "200"
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。
示例 3 :
输入: num = "10", k = 2
输出: "0"
解释: 从原数字移除所有的数字,剩余为空就是0。
这道题考察我们对于单调栈的理解。
当然前提我们需要了解,既然从左往后删除,当前数如果比栈顶的数字小时,我们应该持续出栈,直到当前数字比栈顶的数字大,再将数字入栈。
另外,当字符串的所有数字保持升序,我们一个数字都没出栈,但由于k没有使用完,此时应该返回num[:len(num) - k]的结果。
class Solution:
def removeKdigits(self, num: str, k: int) -> str:
result = []
count = len(num) - k
for c in num:
# 如果result中有已经添加的digit,而且当前的digit比result[-1]的digit更小,而且k个机会还没用完,则用当前更小的digit循环代替result中的digit
while result and c < result[-1] and k:
result.pop()
k -= 1
result.append(c)
return ''.join(result[:count]).lstrip("0") or "0"
class Solution {
public:
string removeKdigits(string num, int k) {
string result;
int count = num.size() - k;
for(char c : num){
while(!result.empty() && c < result.back() && k){
result.pop_back();
k--;
}
result.push_back(c);
}
// 删除多余的元素
while(k > 0){
result.pop_back();
k--;
}
// 去除前方的0
string ans = "";
bool isLeadingZero = true;
for (auto c: result) {
if (isLeadingZero && c == '0') {
continue;
}
isLeadingZero = false;
ans += c;
}
return ans == "" ? "0" : ans;
}
};
class Solution:
def removeKdigits(self, num: str, k: int) -> str:
stack = []
idx = 0
result = ""
for c in num:
# 如果stack中有已经添加的digit,而且当前的digit比stack[-1]的digit更小,而且k个机会还没用完,则用当前更小的digit循环代替stack中的digit
while stack and c < stack[-1] and idx < k:
stack.pop()
idx += 1
stack.append(c)
result = ''.join(stack[:len(num) - k]).lstrip('0')
if result:
return result
else:
return '0'
316-去除重复字母
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入:s = "bcabc"
输出:"abc"
示例 2:
输入:s = "cbacdcbc"
输出:"acdb"
提示:
- 1 <= s.length <= 104
- s 由小写英文字母组成
class Solution {
public:
string removeDuplicateLetters(string s) {
string result;
unordered_map<char, int> map;
for(char c : s){
map[c]++;
}
for(auto c : s){
if(result.find(c) == -1){
while(result.size() != 0 && c < result.back() && map[result.back()] > 0){
result.pop_back();
}
result.push_back(c);
}
map[c]--;
}
return result;
}
};
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
result=[]
dict = {} # dict[c] 表示之后时间里c会出现的次数 初始化为每个字母的频数 遍历过程中减小
for c in s:
dict[c] = dict.get(c, 0) + 1
for c in s:
if c not in result: # c不在result中时再考虑是否添加
while result and c < result[-1] and dict[result[-1]] > 0: # 添加前查看是否pop 注意这里是while
result.pop()
result.append(c)
dict[c]-=1
return ''.join(result)
1081-不同字符的最小子序列【同“316-去除重复字母”】
456-132 模式
给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。
如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。
进阶:很容易想到时间复杂度为 O(n^2) 的解决方案,你可以设计一个时间复杂度为 O(n logn) 或 O(n) 的解决方案吗?
示例 1:
输入:nums = [1,2,3,4]
输出:false
解释:序列中不存在 132 模式的子序列。
示例 2:
输入:nums = [3,1,4,2]
输出:true
解释:序列中有 1 个 132 模式的子序列: [1, 4, 2] 。
示例 3:
输入:nums = [-1,3,2,0]
输出:true
解释:序列中有 3 个 132 模式的的子序列:[-1, 3, 2]、[-1, 3, 0] 和 [-1, 2, 0] 。
提示:
- n == nums.length
- 1 < = n < = 1 0 4 1 <= n <= 10^4 1<=n<=104
- − 1 0 9 < = n u m s [ i ] < = 1 0 9 -10^9 <= nums[i] <= 10^9 −109<=nums[i]<=109
方法一:暴力解法【 O ( n 2 ) O(n^2) O(n2)】
class Solution:
def find132pattern(self, nums):
N = len(nums)
numsi = nums[0]
for j in range(1, N):
for k in range(N - 1, j, -1):
if numsi < nums[k] < nums[j]:
return True
numsi = min(numsi, nums[j])
return False
方法二:单调栈【单调递减栈】
class Solution:
def find132pattern(self, nums: List[int]) -> bool:
stack = [] # stack 维持一个单调递减的栈
s2 = float('-inf')
for num in reversed(nums):
if num < s2: # num 维护 132 模式中的 1,如果出现,则表示出现了 132 模式
return True
while stack and num > stack[-1]: # 如果出现 32 样式,则将stack中的2弹出,将3装入stack,并将2赋值给s2;如果始终不出现32样式,则s2一直为 -inf
s2 = stack.pop() # s2 维护 132 模式中的 2【通过while循环,s2始终维护着全局最大的 2样式数据】
stack.append(num)
return False
class Solution {
public:
bool find132pattern(vector<int>& nums) {
stack<int> stk; // 维持一个单调递减的栈
int s2 = INT_MIN;
reverse(nums.begin(), nums.end());
for(auto num : nums){
if(num < s2){ // num 维护 132 模式中的 1,如果出现,则表示出现了 132 模式
return true;
}
while(!stk.empty() and num > stk.top()){ // 如果出现 32 样式,则将stack中的2弹出,将3装入stack,并将2赋值给s2;如果始终不出现32样式,则s2一直为 -inf
s2 = stk.top(); // s2 维护 132 模式中的 2【通过while循环,s2始终维护着全局最大的 2样式数据】
stk.pop();
}
stk.push(num);
}
return false;
}
};
496. 下一个更大元素 I【单调栈】
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
提示:
- 1 <= nums1.length <= nums2.length <= 1000
- 0 < = n u m s 1 [ i ] , n u m s 2 [ i ] < = 1 0 4 0 <= nums1[i], nums2[i] <= 10^4 0<=nums1[i],nums2[i]<=104
- nums1和nums2中所有整数 互不相同
- nums1 中的所有整数同样出现在 nums2 中
进阶:你可以设计一个时间复杂度为 O(nums1.length + nums2.length) 的解决方案吗?
from typing import List
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
stack = [] # 用于构建单调栈,维护nums2的单调递减序列
dict = {}
# [1, 3, 4, 2, 6, 5]
# 倒序遍历nums2
for num in reversed(nums2):
while stack and num > stack[-1]: # 如果当前元素 > 后面的元素,则将后面的元素弹出,压入前面的元素
stack.pop()
if stack == []: # 如果stack中没有元素,说明nums2中没有比当前元素num大的元素
dict[num] = -1
else: # 如果stack中有元素,说明nums2中有比当前元素num大的元素,最近的元素则是stack[-1]
dict[num] = stack[-1]
stack.append(num) # 将当前元素压入stack中
result = []
for num in nums1:
result.append(dict[num])
return result
solution = Solution()
nums1 = [4, 5, 1, 2]
nums2 = [1, 3, 4, 2, 6, 5]
result = solution.nextGreaterElement(nums1, nums2)
print("result = ", result)
打印结果:
result = [6, -1, 3, 6]
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> stk; // 用于构建单调栈,维护nums2的单调递减序列
unordered_map<int, int> map;
// 倒序遍历nums2
for(auto it = nums2.rbegin(); it != nums2.rend(); it++){
std::cout << "*it = " << *it << std::endl;
while(!stk.empty() and *it > stk.top()){ // 如果当前元素 > 后面的元素,则将后面的元素弹出,压入前面的元素
stk.pop();
}
if(stk.empty()){ // 如果stack中没有元素,说明nums2中没有比当前元素num大的元素
map[*it] = -1;
}else{ // 如果stack中有元素,说明nums2中有比当前元素num大的元素,最近的元素则是stack[-1]
map[*it] = stk.top();
}
stk.push(*it); // 将当前元素压入stack中
}
vector<int> result;
for(auto num : nums1){
result.push_back(map[num]);
}
return result;
}
};
503. 下一个更大元素 II【单调栈】
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例 1:
输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
注意: 输入数组的长度不会超过 10000。
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> result(n); // 存放结果
stack<int> stk;
// 假装这个数组长度翻倍了
for (int i = 2 * n - 1; i >= 0; i--) {
int loc = i % n;
int num = nums[loc];
while (!stk.empty() && num >= stk.top()){
stk.pop();
}
if(stk.empty()){
result[loc] = -1;
}else{
result[loc] = stk.top();
}
stk.push(num);
}
return result;
}
};
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
n = len(nums)
result = [-1] * n
stk = []
for i in range(n * 2 - 1, -1, -1):
loc = i % n
num = nums[loc]
while stk and num >= stk[-1]:
stk.pop()
if stk == []:
result[loc] = -1
else:
result[loc] = stk[-1]
stk.append(num)
return result
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
nums2 = nums + nums
stack = [] # 用于构建单调栈,维护nums2的单调递减序列
result = []
# [1,2,1][1,2,1]
# 倒序遍历nums2
for num in reversed(nums2):
while stack and num >= stack[-1]: # 如果当前元素 > 后面的元素,则将后面的元素弹出,压入前面的元素
stack.pop()
if stack == []: # 如果stack中没有元素,说明nums2中没有比当前元素num大的元素
result.append(-1)
else: # 如果stack中有元素,说明nums2中有比当前元素num大的元素,最近的元素则是stack[-1]
result.append(stack[-1])
stack.append(num) # 将当前元素压入stack中
return result[::-1][:len(result)//2]
from typing import List
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
nums2 = nums + nums
stack = [] # 用于构建单调栈,维护nums2的单调递减序列
result = []
# [100, 1, 11, 1, 120, 111, 123, 1, -1, -100][100, 1, 11, 1, 120, 111, 123, 1, -1, -100]
# 倒序遍历nums2
for num in reversed(nums2):
while stack and num >= stack[-1]: # 如果当前元素 >= 后面的元素,则将后面的元素弹出,压入前面的元素
stack.pop()
if stack == []: # 如果stack中没有元素,说明nums2中没有比当前元素num大的元素
result.append(-1)
else: # 如果stack中有元素,说明nums2中有比当前元素num大的元素,最近的元素则是stack[-1]
result.append(stack[-1])
stack.append(num) # 将当前元素压入stack中
return result[::-1][:len(result) // 2]
solution = Solution()
nums = [100, 1, 11, 1, 120, 111, 123, 1, -1, -100]
result = solution.nextGreaterElements(nums)
print("result = ", result)
打印结果:
result = [120, 11, 120, 120, 123, 123, -1, 100, 100, 100]
739-每日温度【剑指 Offer II 038. 每日温度】
请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
逆向遍历:
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> result;
stack<int> stk;
int idx = temperatures.size() - 1;
for(auto it = temperatures.rbegin(); it != temperatures.rend(); it++){
while(!stk.empty() && *it >= temperatures[stk.top()]){
stk.pop();
}
if(stk.empty()){
result.push_back(0);
}else{
result.push_back(stk.top() - idx);
}
stk.push(idx);
idx--;
}
reverse(result.begin(), result.end());
return result;
}
};
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
result = []
stack = []
for idx in range(len(temperatures) - 1, -1, -1):
while stack and temperatures[idx] >= temperatures[stack[-1]]:
stack.pop()
if stack == []:
result.append(0)
else:
result.append(stack[-1] - idx)
stack.append(idx)
return result[::-1]
正向遍历:
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> result(n);
stack<int> s;
for (int i = 0; i < n; i++) {
while (!s.empty() && temperatures[i] > temperatures[s.top()]) {
int preIndex = s.top();
result[preIndex] = i - preIndex;
s.pop();
}
s.push(i);
}
return result;
}
};
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
length = len(temperatures)
result = [0] * length
stack = []
for i in range(length):
temperature = temperatures[i]
while stack and temperature > temperatures[stack[-1]]:
prev_index = stack.pop()
result[prev_index] = i - prev_index
stack.append(i)
return result
255. 验证前序遍历序列二叉搜索树
给定一个整数数组,你需要验证它是否是一个二叉搜索树正确的先序遍历序列。
你可以假定该序列中的数都是不相同的。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:
输入: [5,2,6,1,3]
输出: false
示例 2:
输入: [5,2,1,3,6]
输出: true
进阶挑战:您能否使用恒定的空间复杂度来完成此题?
方法一:单调栈【单调递减栈】
二叉搜索树的前序遍历:先递减,后递增
- 左子树是递减序列
- 右子树是递增序列
维护一个单调递减栈
我们可以通俗理解为“总体递增,局部递减”。为了达到“总体递增的效果”,我们要保证递减序列的第一个元素小于递减结束后即将递增的那个元素。因此,我们我们使用new_min和栈,如果递减结束后,下一个元素小于递减序列的第一个元素,违背了“总体递增”,立即返回False。
- BST的性质是左子树小于root,右子树大于root,所以校验这个性质即可。
- 单调递减栈的单调性用来假设root到左子树的性质正确,再用待push进栈的节点值必须大于已经pop出来的所有元素的值来校验root到右子树的性质。
- 得益于单调性,已经pop出来的所有元素的值是不断递增的,所以用最新的值来校验即可。
from typing import List
# 二分搜索树的前序遍历:先降序,后升序
class Solution:
# [10, 5, 2, 1, 3, 6, 8, 9]
def verifyPreorder(self, preorder: List[int]) -> bool:
stack = [] # 维护一个单调递减栈
new_min = float('-inf') # 初始化升序序列部分的下限值
for num in preorder:
# 当开始升序后,要一直递增下去
if num < new_min:
return False
while stack and num > stack[-1]: # 当 num > stack[-1] 时, 说明开始升序
new_min = stack.pop()
stack.append(num)
return True
solution = Solution()
nums = [10, 5, 2, 1, 3, 6, 11, 19] # [10, 5, 2, 1, 3, 6, 11, 19], [10, 5, 2, 1, 3, 11, 6, 19]
result = solution.verifyPreorder(nums)
print("result = ", result)
剑指 Offer 33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
提示:数组长度 <= 1000
方法一:单调栈【单调递增栈】
维护一个单调递增栈
后序遍历倒序: [ 根节点 | 右子树 | 左子树 ] 。类似 先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。
在遍历过程中
- 我们将根节点压栈,然后遍历右子树,若是右子树存在,则序列的下一个数将大于根节点并且该结点就是右子树的根节点,同理压入栈中,以此类推,直到序列的下一个数小于根结点了,说明这棵子树没有右子树了,
- 但序列的下一个数并不一定是当前子树根结点的左孩子结点,也有可能是其父节点或祖父结点的左孩子结点,我们必须在祖先结点中找到比序列中下一个结点大的最小值作为序列中下一个结点的父节点(这里有点难理解,需结合二叉树 左<根<右 的性质和父子兄弟结点的大小关系思考为什么是这样),
- 所以我们将栈中结点依次出栈(因为栈中的是当前结点的一系列祖先结点),直到栈顶结点小于序列的下一个结点,
- 那么最后一个出栈的就是祖先结点中比序列中下一个结点大的最小结点,也就是序列中下一个结点的父节点,
- 我们再将序列下一个结点入栈,重复上述过程,最后就能根据后序遍历和BST的性质还原二叉搜索树
举个例子:[4, 8, 6, 12, 16, 14, 10]
10
/ \
6 14
/ \ / \
4 8 12 16
- 10作为根节点入栈,此时栈:[10]
- 14比栈顶元素10大,说明是10的右子树的根节点,入栈,此时栈:[10,14]
- 16比栈顶元素14大,说明是14的右子树的根节点,入栈,此时栈:[10,14,16]
- 12比栈顶元素16小,说明栈顶元素16没有右子树,这时我们要去栈中寻找12的父节点,即比12大的最小祖先结点
- 16,14均比12大,依次出栈,此时栈’[10]`
- 栈顶元素比12小,那么最后出栈的14就是比12大的最小祖先结点,即12的父节点,所以12是14的左孩子结点,最后将12入栈,此时栈[10,12]
- 6比栈顶元素12小,说明栈顶元素12没有右子树,依次将12,10出栈,此时栈空,所以最后出栈的结点10就是6的父节点,6是10的左孩子结点,此时栈 [6]
- 8比栈顶元素6大,说明是6的右子树的根节点,入栈,此时栈:[6,8]
- 4比8,6小,依次出栈,4是最后出栈元素6的左孩子结点
- 序列遍历结束,最后一个值无需再入栈了,得到如下二叉搜索树
class Solution:
def verifyPostorder(self, postorder: [int]) -> bool:
stack = [] # 维护一个单调递增栈
root = float("+inf")
for num in reversed(postorder):
if num > root:
return False
while stack and num < stack[-1]: # 当 num < stack[-1] 时, 说明进入左子树
root = stack.pop()
stack.append(num)
return True
solution = Solution()
nums = [1, 3, 2, 6, 5] # [1, 3, 2, 6, 5], [1,6,3,2,5]
result = solution.verifyPostorder(nums)
print("result = ", result)
面试题 16.16. 部分排序
给定一个整数数组,编写一个函数,找出索引m和n,只要将索引区间[m,n]的元素排好序,整个数组就是有序的。注意:n-m尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n],若不存在这样的m和n(例如整个数组是有序的),请返回[-1,-1]。
示例:
输入: [1,2,4,7,10,11,7,12,6,7,16,18,19]
输出: [3,9]
提示:0 <= len(array) <= 1000000
class Solution:
def subSort(self, array: List[int]) -> List[int]:
n = len(array)
max_val = float('-inf')
min_val = float('inf')
left, right = -1, -1
# 正向维护一个递增,最后一个下降值处的index即为right
for i in range(n):
if array[i] < max_val:
right = i
else:
max_val = array[i]
# 反向维护一个递减,最后一个上升处的index即为left
for i in range(n-1, -1, -1):
if array[i] > min_val:
left = i
else:
min_val = array[i]
return [left, right]
二、队列(Queue)
225-用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通队列的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
- void push(int x) 将元素 x 压入栈顶。
- int pop() 移除并返回栈顶元素。
- int top() 返回栈顶元素。
- boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
- 你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
- 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
提示:
- 1 <= x <= 9
- 最多调用100 次 push、pop、top 和 empty
- 每次调用 pop 和 top 都保证栈不为空
进阶:你能否实现每种操作的均摊时间复杂度为 O(1) 的栈?换句话说,执行 n 个操作的总时间复杂度 O(n) ,尽管其中某个操作可能需要比其他操作更长的时间。你可以使用两个以上的队列。
方法一:用1个队列实现1个栈
class MyStack:
def __init__(self):
"""
Initialize your data structure here.
"""
self.queue = []
def push(self, x: int) -> None:
"""
Push element x onto stack.
"""
self.queue.append(x)
for _ in range(len(self.queue) - 1):
self.queue.append(self.queue.pop(0))
def pop(self) -> int:
"""
Removes the element on top of the stack and returns that element.
"""
return self.queue.pop(0)
def top(self) -> int:
"""
Get the top element.
"""
return self.queue[0]
def empty(self) -> bool:
"""
Returns whether the stack is empty.
"""
return len(self.queue) == 0
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
方法二:用2个队列实现1个栈
class MyStack:
def __init__(self):
"""
Initialize your data structure here.
"""
self.queue01 = []
self.queue02 = []
def push(self, x: int) -> None:
"""
Push element x onto stack.
"""
self.queue02.append(x)
while self.queue01:
self.queue02.append(self.queue01.pop(0))
self.queue01, self.queue02 = self.queue02, self.queue01
def pop(self) -> int:
"""
Removes the element on top of the stack and returns that element.
"""
return self.queue01.pop(0)
def top(self) -> int:
"""
Get the top element.
"""
return self.queue01[0]
def empty(self) -> bool:
"""
Returns whether the stack is empty.
"""
return len(self.queue01) == 0 and len(self.queue02) == 0
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
class MyStack {
public:
queue<int> queue1;
queue<int> queue2;
MyStack() {
}
void push(int x) {
queue2.push(x);
while(!queue1.empty()){
queue2.push(queue1.front());
queue1.pop();
}
swap(queue1, queue2);
}
int pop() {
int r = queue1.front();
queue1.pop();
return r;
}
int top() {
return queue1.front();
}
bool empty() {
return queue1.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();
*/
剑指 Offer 59 - II. 队列的最大值
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
限制:
- 1 <= push_back,pop_front,max_value的总操作数 <= 10000
- 1 <= value <= 1 0 5 10^5 105
利用 数据结构 来实现,即经常使用的 “空间换时间” 。如下图所示,考虑构建一个递减列表来保存队列 所有递减的元素 ,递减链表随着入队和出队操作实时更新,这样队列最大元素就始终对应递减列表的首元素,实现了获取最大值 O(1) 时间复杂度。
为了实现此递减列表,需要使用 双向队列 ,假设队列已经有若干元素:
- 当执行入队 push_back() 时: 若入队一个比队列某些元素更大的数字 x ,则为了保持此列表递减,需要将双向队列 尾部所有小于 x 的元素 弹出。
- 当执行出队 pop_front() 时: 若出队的元素是最大元素,则 双向队列 需要同时 将首元素出队 ,以保持队列和双向队列的元素一致性。
使用双向队列原因:维护递减列表需要元素队首弹出、队尾插入、队尾弹出操作皆为 O(1)O(1) 时间复杂度。
函数设计:初始化队列 queue ,双向队列 deque ;
最大值 max_value() :
- 当双向队列 deque 为空,则返回 −1 ;
- 否则,返回 deque 首元素;
入队 push_back() :
- 将元素 value 入队 queue ;
- 将双向队列中队尾 所有 小于 value 的元素弹出(以保持 deque 非单调递减),并将元素 value 入队 deque ;
出队 pop_front() :
- 若队列 queue 为空,则直接返回 −1 ;
- 否则,将 queue 首元素出队;
- 若 deque 首元素和 queue 首元素 相等 ,则将 deque 首元素出队(以保持两队列 元素一致 ) ;
设计双向队列为 单调不增 的原因:若队列 queue 中存在两个 值相同的最大元素 ,此时 queue 和 deque 同时弹出一个最大元素,而 queue 中还有一个此最大元素;即采用单调递减将导致两队列中的元素不一致。
class MaxQueue {
public:
queue<int> que;
deque<int> deq;
MaxQueue() {}
int max_value() {
return !deq.empty() ? deq.front(): -1;
}
void push_back(int value) {
que.push(value);
while(!deq.empty() && value > deq.back()){
deq.pop_back();
}
deq.push_back(value);
}
int pop_front() {
if(que.empty()){
return -1;
}
if(que.front() == deq.front()){
deq.pop_front();
}
int result = que.front();
que.pop();
return result;
}
};
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue* obj = new MaxQueue();
* int param_1 = obj->max_value();
* obj->push_back(value);
* int param_3 = obj->pop_front();
*/
class MaxQueue:
def __init__(self):
self.queue = []
self.max_queue = []
def max_value(self) -> int:
return self.max_queue[0] if self.max_queue else -1
def push_back(self, value: int) -> None:
self.queue.append(value)
while self.max_queue and value > self.max_queue[-1]:
self.max_queue.pop()
self.max_queue.append(value)
print("\nqueue = ", self.queue)
print("max_queue = ", self.max_queue)
def pop_front(self) -> int:
if not self.queue:
return -1
if self.queue[0] == self.max_queue[0]:
self.max_queue.pop(0)
return self.queue.pop(0)
# Your MaxQueue object will be instantiated and called as such:
# obj = MaxQueue()
# param_1 = obj.max_value()
# obj.push_back(value)
# param_3 = obj.pop_front()
输入:
["MaxQueue","push_back","push_back","push_back","push_back","push_back","push_back","push_back","push_back","max_value","pop_front","max_value"]
[[],[6],[2],[5],[7],[3],[1],[-1],[4],[],[],[]]
输出:
[null,null,null,null,null,null,null,null,null,7,6,7]
打印结果:
queue = [6]
max_queue = [6]
queue = [6, 2]
max_queue = [6, 2]
queue = [6, 2, 5]
max_queue = [6, 5]
queue = [6, 2, 5, 7]
max_queue = [7]
queue = [6, 2, 5, 7, 3]
max_queue = [7, 3]
queue = [6, 2, 5, 7, 3, 1]
max_queue = [7, 3, 1]
queue = [6, 2, 5, 7, 3, 1, -1]
max_queue = [7, 3, 1, -1]
queue = [6, 2, 5, 7, 3, 1, -1, 4]
max_queue = [7, 4]
239-滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
示例 3:
输入:nums = [1,-1], k = 1
输出:[1,-1]
示例 4:
输入:nums = [9,11], k = 2
输出:[11]
示例 5:
输入:nums = [4,-2], k = 2
输出:[4]
提示:
- 1 < = n u m s . l e n g t h < = 1 0 5 1 <= nums.length <= 10^5 1<=nums.length<=105
- − 1 0 4 < = n u m s [ i ] < = 1 0 4 -10^4 <= nums[i] <= 10^4 −104<=nums[i]<=104
- 1 <= k <= nums.length
剑指 Offer 59 - I-滑动窗口的最大值
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
注意:本题与主站 239 题相同
三、堆
母题
给你 k 个有序的非空数组,让你从每个数组中分别挑一个,使得二者差的绝对值最小。
def f(matrix):
result = float('inf')
max_value = max(nums[0] for nums in matrix)
heap = [(nums[0], i, 0) for i, nums in enumerate(matrix)]
heapq.heapify(heap)
while True:
min_value, row, idx = heapq.heappop(heap)
if max_value - min_value < result:
result = max_value - min_value
if idx == len(matrix[row]) - 1:
break
max_value = max(max_value, matrix[row][idx + 1])
heapq.heappush(heap, (matrix[row][idx + 1], row, idx + 1))
return result
剑指 Offer 40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
- 0 <= k <= arr.length <= 10000
- 0 <= arr[i] <= 10000
这道题是一个经典的 Top K 问题,是面试中的常客。Top K 问题有两种不同的解法:
- 一种解法使用堆(优先队列);
- 另一种解法使用类似快速排序的分治法。
这两种方法各有优劣,最好都掌握。
方法一:堆
比较直观的想法是使用堆数据结构来辅助得到最小的 k 个数。堆的性质是每次可以找出最大或最小的元素。我们可以使用一个大小为 k 的最大堆(大顶堆),将数组中的元素依次入堆,当堆的大小超过 k 时,便将多出的元素从堆顶弹出。我们以数组
[
5
,
4
,
1
,
3
,
6
,
2
,
9
]
[5,4,1,3,6,2,9]
[5,4,1,3,6,2,9],
k
=
3
k=3
k=3 为例展示元素入堆的过程,如下面动图所示:
这样,由于每次从堆顶弹出的数都是堆中最大的,最小的 k 个元素一定会留在堆里。这样,把数组中的元素全部入堆之后,堆中剩下的 k 个元素就是最大的 k 个数了。
注意在动画中,我们并没有画出堆的内部结构,因为这部分内容并不重要。我们只需要知道堆每次会弹出最大的元素即可。在写代码的时候,我们使用的也是库函数中的优先队列数据结构,如 C++中的 priority_queue(默认大根堆),Java 中的 PriorityQueue(默认大根堆),Python中的heap(默认小根堆)。在面试中,我们不需要实现堆的内部结构,把数据结构使用好,会分析其复杂度即可。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> result;
priority_queue<int> q; // 大根堆:弹出的是大值,留下来的是小值
for (int i = 0; i < arr.size(); i++) {
q.push(arr[i]);
if(i >= k){
q.pop();
}
}
for (int i = 0; i < k; ++i) {
result.push_back(q.top());
q.pop();
}
return result;
}
};
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
result = []
heap = [] # 小根堆:弹出的是小值,留下来的是大值
for idx, num in enumerate(arr):
heapq.heappush(heap, num)
if idx >= len(arr) - k:
result.append(heapq.heappop(heap))
return result
参考资料:
单调栈总结