1. 括号匹配
题目:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
输入:s = "()[]{}"
输出:true
输入:s = "{[]}"
输出:true
思路:左push右pop
字符串中为左括号时,右括号入栈比直接左括号入栈要简单,因为到时候只要匹配栈顶元素相不相等即可。
匹配的情况为:字符串遍历完,栈也空了。
有三种不匹配的情况,一定要考虑全:
-
字符串中左边括号多余——字符串完,栈非空。
-
括号不多余,但是类型不对应——栈中没有字符串要匹配的内容。
-
字符串中右边括号多余——栈空,字符串未遍历完。
笔记:Python有两种方法:
- 同C++仅用栈
- 一种栈+字典
C++:
class Solution {
public:
bool isValid(string s) {
// 首先把s长度为奇数的淘汰
if (s.size() % 2 != 0) return false;
stack<char> st;
for (int i = 0; i < s.size(); i++){
if (s[i] == '(') st.push(')');
else if (s[i] == '[') st.push(']');
else if (s[i] == '{') st.push('}');
// 2 or 3: 字符串没完,栈空 类型不匹配
else if (st.empty() || st.top() != s[i]) return false;
else st.pop();
}
// 都空就匹配,栈非空就是1
return st.empty();
}
};
Python:
# 方法一,仅使用栈,更省空间
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for i in s:
if i == '(':
stack.append(')')
elif i == '[':
stack.append(']')
elif i == '{':
stack.append('}')
elif not stack or stack[-1] != i:
return False
else:
stack.pop()
return True if not stack else False
# 方法二,使用字典
class Solution:
def isValid(self, s: str) -> bool:
stack = []
mapping = {
'(': ')',
'[': ']',
'{': '}'
}
for i in s:
if i in mapping.keys():
stack.append(mapping[i])
elif not stack or stack[-1] != i:
return False
else:
stack.pop()
return True if not stack else False
2. 删除字符串中的所有相邻重复项
题目:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
输入:"abbaca"
输出:"ca"
思路:消消乐,i与栈顶元素相同就消除,不同就入栈。注意C++最后return的值还要新建一个string来放,不能直接return stack。
笔记:C++ string front()
函数表示获取字符串第一个字符,back()
函数表示获取字符串最后一个字符.
注:Python中list是有pop()
函数的,而且还返回值。
C++:
class Solution {
public:
string removeDuplicates(string s) {
stack<char> st;
for (int i = 0; i < s.size(); i++){
if (!st.empty() && st.top() == s[i]){
st.pop();
}
else st.push(s[i]);
}
string result = "";
while(!st.empty()){
result += st.top();
st.pop();
}
reverse (result.begin(), result.end());
return result;
}
};
法二,不创建栈,直接用string做栈
class Solution {
public:
string removeDuplicates(string S) {
string result;
for(char s : S){
if(!result.empty() && result.back() == s){
// 从string尾pop()
result.pop_back();
}
else {
result.push_back(s);
}
}
return result;
}
};
Python:
class Solution:
def removeDuplicates(self, s: str) -> str:
res = []
for i in s:
if res and res[-1] == i:
res.pop()
else:
res.append(i)
return "".join(res)
Python不用栈的话,可以用双指针,fast控制遍历list(s),slow用来相同就回退。
class Solution:
def removeDuplicates(self, s: str) -> str:
res = list(s)
slow = fast = 0
length = len(res)
while fast < length:
# 如果一样直接换,不一样会把后面的填在slow的位置
res[slow] = res[fast]
# 如果发现和前一个一样,就退一格指针
if slow > 0 and res[slow] == res[slow - 1]:
slow -= 1
else:
slow += 1
fast += 1
return ''.join(res[0: slow])
3. 逆波兰(后缀)表达式求值
题目:有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
笔记:
C++
stoi()函数可以将string类型转为int型,负数也可以,遇到任何不是数字的字符就丢弃后面,所以会舍弃小数。
Python
-
Python中isdigit()函数可以直接判断字符串是不是数字,但是遇到负数返回False,因为 ‘-’ 它看做字符。
-
这题要注意看题目:两个整数之间的除法只保留整数部分。
不是四舍五入,但又不能直接用//,因为负数-0.04会被算为-1,但应该为0.
正确做法:int(b / a)
注意:Python中除法
//: 表示地板除(不管小数):5 // 3 = 1 (5 ÷ 3 = 1.6666666666666667)
/: 结果为浮点数
round(a/b): 表示四舍五入除
- Python 的 eval()函数用于去掉“”然后执行转化后的语句。
- Python中
f'{}'
相当于format{}函数。
C++:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for (int i = 0; i < tokens.size(); i++) {
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
int num1 = st.top();
st.pop();
int num2 = st.top();
st.pop();
if (tokens[i] == "+") st.push(num2 + num1);
if (tokens[i] == "-") st.push(num2 - num1);
if (tokens[i] == "*") st.push(num2 * num1);
if (tokens[i] == "/") st.push(num2 / num1);
} else {
st.push(stoi(tokens[i]));
}
}
int result = st.top();
st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
return result;
}
};
Python:
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for i in tokens:
if i not in ['+','-','*','/']:
stack.append(i)
else:
num2 = int(stack.pop())
num1 = int(stack.pop())
if i == '+':
stack.append(num1 + num2)
elif i == '-':
stack.append(num1 - num2)
elif i == '*':
stack.append(num1 * num2)
elif i == '/':
stack.append(int(num1 / num2))
return int(stack[0])
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for i in tokens:
if i not in {"+", "-", "*", "/"}:
stack.append(i)
else:
num2, num1 = stack.pop(), stack.pop()
stack.append(
int(eval(f'{num1} {i} {num}')) # 第一个出来的在运算符后面
)
return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的
4. 滑动窗口的最大值
题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 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
思路:
为什么不可以设一个maxV的值,每次移动的时候对比一下nums[i]和maxV谁大,大就放到result里呢?
注意审题!题目要求的是当前窗口内的最大值,而上述方法若滑动时最大值被pop出去了,后面maxV仍然不会改。所以,要么暴力,每次都在窗口内找一遍,要么对队列进行排序。
单调队列
单调队列的 push 方法依然在队尾添加元素,但是要把前面比新元素小的元素都删掉
笔记:
class deque {
// 在队头插入元素 n
void push_front(int n);
// 在队尾插入元素 n
void push_back(int n);
// 在队头删除元素
void pop_front();
// 在队尾删除元素
void pop_back();
// 返回队头元素
int front();
// 返回队尾元素
int back();
}
C++:
class Solution {
private:
// 单调队列
class MyQueue{
public:
deque<int> que;
// 实际上, 只有当要弹最大值时, 才会真正用到pop(), 比最大值小的数之前push()的时候就弹出去了
void pop(int x){
if (!que.empty() && x == que.front()){
que.pop_front(); //双端队列,从前端弹出
}
}
// 保持push()进来后que是从大到小的顺序,即把x前比它小的数全部pop出去
void push(int x){
while (!que.empty() && x > que.back()){
que.pop_back(); // 因为这里x是和队尾比,所以pop也是先pop队尾
}
que.push_back(x);
}
int front(){
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> result;
// 先把前k个元素push进来
for(int i = 0; i < k; i++){
que.push(nums[i]);
}
result.push_back(que.front());
for(int i = k; i < nums.size(); i++){
que.pop(nums[i - k]); // 滑动窗口移除最前面元素
que.push(nums[i]);
result.push_back(que.front());
}
return result;
}
};
Python:
class MyQueue:
def __init__(self):
self.queue = []
def pop(self, x):
if self.queue and x == self.queue[0]:
self.queue.pop(0)
def push(self, x):
while self.queue and x > self.queue[-1]:
self.queue.pop()
self.queue.append(x)
def front(self):
return self.queue[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
que = MyQueue()
result = []
for i in range(k):
que.push(nums[i])
result.append(que.front())
for i in range(k, len(nums)):
que.pop(nums[i - k])
que.push(nums[i])
result.append(que.front())
return result
5. 前 K 个高频元素
题目:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
思路:
笔记:
Python中的dict相当于C++中的unordered_map
d[k]=d.get(k,0)+1
用于统计字典d中k键出现的次数,默认值为0。
get()
函数原本用于获得k键对应的值。
Python中heapq库是最小堆
# 往堆里面填入值
heapq.heappush(heap, item)
# 将列表转换为堆
heapq.heapify(list)
# 将堆顶元素pop出来
heapq.heappop(heap)
C++ 优先队列
priority_queue<Type, Container, Functional>;
Type是要存放的数据类型
Container是实现底层堆的容器,必须是数组实现的容器,如vector、deque
Functional是比较方式/比较函数/优先级
priority_queue<Type>;
此时默认的容器是vector,默认的比较方式是大顶堆
//小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//大顶堆
priority_queue <int,vector<int>,less<int> >q;
//默认大顶堆
priority_queue<int> a;
自定义比较方式
当数据类型并不是基本数据类型,而是自定义的数据类型时,就不能用greater或less的比较方式了,而是需要自定义比较方式
在此假设数据类型是自定义的水果:
struct fruit
{
string name;
int price;
};
有两种自定义比较方式的方法,如下
- 重载运算符
重载”<”
注意:从大到小排建小顶堆,从小到大排建大顶堆。
- 若希望水果价格高为优先级高,则
//大顶堆
struct fruit
{
string name;
int price;
friend bool operator < (fruit f1,fruit f2)
{
return f1.peice < f2.price;
}
};
- 若希望水果价格低为优先级高
//小顶堆
struct fruit
{
string name;
int price;
friend bool operator < (fruit f1,fruit f2)
{
return f1.peice > f2.price; //此处是>
}
};
- 仿函数
高优先级
//大顶堆
struct myComparison
{
bool operator () (fruit f1,fruit f2)
{
return f1.price < f2.price;
}
};
C++:
class Solution {
public:
// 小顶堆
class mycomparison{
public:
bool operator()(pair<int, int> &lh, pair<int, int> &rh){
return lh.second > rh.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 1.统计元素出现的频率
unordered_map<int, int> map; // map<nums[i], 出现的次数>
// 也可以写为:for(auto& c:nums)
for(int i = 0; i < nums.size(); i++){
map[nums[i]]++;
}
// 2.创建优先队列
priority_queue<pair<int,int>, vector<pair<int,int>>, mycomparison> pri_que;
// 3.遍历map中的元素
// 不管大小,先入队,优先队列会自己排序
// 若队列元素超过k,把栈顶元素弹出
for (unordered_map<int,int>::iterator it = map.begin(); it != map.end(); it++){
pri_que.push(*it);
if(pri_que.size() > k){
pri_que.pop();
}
}
//另一种写法:
//for(auto& a:map){
// pri_que.push(a);
// if(pri_que.size() > k){
// pri_que.pop();
// }
//}
// 4.倒序输出结果
vector<int> result(k);
for(int i = k - 1; i >= 0; i--){
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};
Python:
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
# 统计各元素的频率
freq = {}
for i in range(len(nums)):
freq[nums[i]] = freq.get(nums[i], 0) + 1
# 定义小顶堆
pri_que = []
# 遍历字典,填入小顶堆
for i, f in freq.items():
# 注意:这里是按频率从大到小来排的,所以元组用(f,i)
heapq.heappush(pri_que, (f, i))
if len(pri_que) > k:
heapq.heappop(pri_que)
# 倒序输出结果
result = [0] * k
for i in range(k-1, -1, -1):
result[i] = heapq.heappop(pri_que)[1]
return result