LeetCode20. 有效的括号
https://leetcode-cn.com/problems/valid-parentheses/
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
输入:s = "()"
输出:true
思路
使用栈辅助解题。
分析三种不匹配的情况:
1.字符串里左括号多余 ,所以不匹配。
(()[]{} //左括号多余
2.字符串里括号没有多余,但是 括号的类型没有匹配上。
(] //括号的类型没有匹配上
3.字符串里右括号多余,所以不匹配。
()[]{}) //右括号多余
代码只要覆盖了这三种不匹配的情况,就不会出问题。
代码
class Solution
{
public:
bool isValid(string s)
{
int sz = s.size();
if (sz % 2) return false;
stack<char> stk;
for (int i = 0; i < sz; ++i)
{
if (s[i] == '(') stk.push(')');
else if (s[i] == '[') stk.push(']');
else if (s[i] == '{') stk.push('}');
//字符串中右括号多余或括号类型没有匹配上
else if (stk.empty() || s[i] != stk.top()) return false;
else stk.pop();
}
//左括号多余
return stk.empty();
}
};
LeetCode150. 逆波兰表达式求值
https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
思路
使用栈辅助解题。
代码
class Solution {
public:
int evalRPN(vector<string>& tokens)
{
int sz = tokens.size();
stack<int> stk;
for (int i = 0; i < sz; ++i)
{
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/")
{
int i = stk.top();
stk.pop();
int j = stk.top();
stk.pop();
int x;
string s = tokens[i];
if (s == "+")
{
x = j + i;
}
if (s == "-")
{
x = j - i;
}
if (s == "*")
{
x = j * i;
}
if (s == "/")
{
x = j / i;
}
stk.push(x);
}
else
{
stk.push(stoi(tokens[i]));
}
}
int result = stk.top();
stk.pop();
return result;
}
};
LeetCode1047. 删除字符串中的所有相邻重复项
https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
思路
使用栈辅助解题。
代码
class Solution {
public:
string removeDuplicates(string S)
{
int sz = S.size();
stack<char> stk;
for (int i = 0; i < sz; ++i)
{
if (stk.empty() || S[i] != stk.top())
{
stk.push(S[i]);
}
else
{
stk.pop();
}
}
string result;
while (!stk.empty())
{
result += stk.top();
stk.pop();
}
reverse(result.begin(), result.end());
return result;
}
};
LeetCode84. 柱状图中最大的矩形
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10
个单位。
输入: [2,1,5,6,2,3]
输出: 10
思路
使用单调栈辅助解题。
-
单调栈分为单调递增栈和单调递减栈
- 单调递增栈即栈内元素保持单调递增的栈
- 单调递减栈即栈内元素保持单调递减的栈
-
操作规则(下面都以单调递增栈为例)
- 如果新的元素比栈顶元素大,就入栈
- 如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小
-
性质
-
栈内的元素是递增的
-
当元素出栈时,说明这个新元素是出栈元素向后找第一个比其小的元素
举个例子栈里是 1 5 6 。
接下来新元素是 2 ,那么 6 需要出栈。
当 6 出栈时,右边 2 代表是 6 右边第一个比 6 小的元素。 -
当元素出栈后,说明新栈顶元素是出栈元素向前找第一个比其小的元素
当 6 出栈时,5 成为新的栈顶,那么 5 就是 6 左边第一个比 6 小的元素。
-
-
代码模板
stack<int> st;
for(int i = 0; i < nums.size(); ++i)
{
while(!st.empty() && nums[i] < st.top())
{
st.pop();
}
st.push(nums[i]);
}
- 图示
有了单调栈的知识那么我们就可以知道,当单调栈有元素出栈时,新元素一定是出栈元素右边第一个比自己小的元素,而新栈顶元素一定是出战元素左边第一个比自己小的元素,这样我们就可以得到对应高度(即出栈元素)矩形的最大宽度,就能计算出矩形面积。
注意需要考虑两种特殊的情况:
-
弹栈的时候,栈为空;
-
遍历完成以后,栈中还有元素;
为此可以我们可以在输入数组的两端加上两个高度为 0 的柱形(哨兵)
有了这两个柱形:
- 最左边的柱形:由于它一定比输入数组里任何一个元素小,它肯定不会出栈,因此栈一定不会为空;
- 最右边的柱形:因为它一定比输入数组里任何一个元素小,它会让所有输入数组里的元素出栈(第 1 个哨兵元素除外)。
代码
class Solution
{
public:
int largestRectangleArea(vector<int>& heights)
{
heights.insert(heights.begin(), 0);
heights.push_back(0);
int sz = heights.size();
int maxArea = 0;
stack<int> stk;
for (int i = 0; i < sz; ++i)
{
while (!stk.empty() && heights[i] < heights[stk.top()])
{
int height = heights[stk.top()];
stk.pop();
int right = i;
int left = stk.top();
int width = right - left - 1;
maxArea = max(maxArea, height * width);
}
stk.push(i);
}
return maxArea;
}
};
LeetCode42. 接雨水
https://leetcode-cn.com/problems/trapping-rain-water/
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
思路
根据题意可得:只有当前柱左右两边都有比自己高的柱子才能接到雨水。我们很自然想到可以使用单调栈来帮助找到当前柱左右两边比自己高的柱。
回顾一下单调栈的性质(以下讨论针对单调递减栈):
- 当元素出栈时,说明这个新元素是出栈元素向后找第一个比其小大的元素
- 当元素出栈后,说明新栈顶元素是出栈元素向前找第一个比其大的元素
所以我们可以用一个单调递减栈找到当前柱左右两边比自己高的柱。
代码
class Solution
{
public:
//只有当前柱左右两边都有比自己高的柱子才能接到雨水,所以用一个单调递减栈找到当前柱左右两边比自己高的柱
int trap(vector<int>& height)
{
int sz = height.size();
if (sz == 0) return 0;
int result = 0;
stack<int> stk;
for (int i = 0; i < sz; ++i)
{
while (!stk.empty() && height[i] > height[stk.top()])
{
int cur = stk.top();
stk.pop();
if (stk.empty())
{
break;
}
int right = i;
int left = stk.top();
int width = right - left - 1;
int high = min(height[right], height[left]) - height[cur];
result += width * high;
}
stk.push(i);
}
return result;
}
};
LeetCode347. 前K个高频元素
https://leetcode-cn.com/problems/top-k-frequent-elements/
给定一个非空的整数数组,返回其中出现频率前k
高的元素。
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
思路
使用优先队列辅助解题。
本题大体可分为三个步骤:
- 统计元素出现频率
- 对频率排序
- 找出前K个高频元素
首先使用关联容器unordered_map统计元素出现的频率:key存储元素,value存储元素出现的频率
然后是对频率进行排序并找出前K个高频元素,这里我们可以使用一种容器适配器priority_queue。
因为要统计前K个高频元素,所以要使用用小顶堆,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前K个高频元素(保证小顶堆的大小始终不超过k)。
代码
class Solution {
public:
struct cmp
{
bool operator() (const pair<int, int>& lhs, const pair<int, int>& rhs)
{
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k)
{
int sz = nums.size();
//统计元素出现频率
unordered_map<int, int> mp;
for (int i = 0; i < sz; ++i)
{
++mp[nums[i]];
}
//定义一个小顶堆,大小始终不超过k
priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> pri_que;
for (auto it = mp.begin(); it != mp.end(); ++it)
{
pri_que.push(*it);
//如果堆的大小超过K,则队列弹出,保证堆的大小始终不超过k
if (pri_que.size() > k)
{
pri_que.pop();
}
}
vector<int> result(k);
for (int i = 0; i < k; ++i)
{
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};
LeetCode239. 滑动窗口的最大值
https://leetcode-cn.com/problems/sliding-window-maximum/
给你一个整数数组 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
思路
使用单调队列辅助解题。
使用单调递减队列维护窗口里的元素:保证队列里的元素数值是由大到小的;并且队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了。
设计单调队列的时候,pop,和push操作要保持如下规则:
- pop(value):如果窗口移除的元素value等于单调递减队列的出口元素,那么队列弹出元素(value有可能成为窗口里最大值的元素,所以要将其弹出),否则不用任何操作(value不可能成为窗口里最大值的元素,所以无需任何操作)。
- push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止(保证队列里的元素数值是由大到小的)。
保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。
代码
class Solution
{
public:
class MyQueue
{
public:
void pop(int val)
{
if (!que.empty() && val == que.front())
{
que.pop_front();
}
}
void push(int val)
{
while (!que.empty() && val > que.back())
{
que.pop_back();
}
que.push_back(val);
}
int front()
{
return que.front();
}
private:
deque<int> que;
};
vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
MyQueue que;
for (int i = 0; i < k; ++i)
{
que.push(nums[i]);
}
vector<int> result;
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;
}
};
int front()
{
return que.front();
}
private:
deque<int> que;
};
vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
MyQueue que;
for (int i = 0; i < k; ++i)
{
que.push(nums[i]);
}
vector<int> result;
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;
}
};