如果只了解栈的特点我们可能不知道栈的作用,在大学备考计算机二级C语言的时候,学习过栈和队列,当时只知道栈的特点是后进先出,队列的特点是先进先出。编程过程中并没有用过栈和队列,没有体会到算法的巧妙。上了研究生为了找工作开始刷leetcode的题目才体会到算法的巧妙。今天是在俄罗斯的第五个半月,也是我开始刷leetcode的第二遍,这一遍我决定记录我自己的刷题经验,以便后来复习,如果能帮助到大家,那更好不过了。
题目一:给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
拿到这个题有可能会想到计算左括号的个数但是这样空间复杂度会比较大,因为你需要开辟一定的空间来存储括号的个数。
这个想到用栈还是比较容易的,我们找个栈只存放左括号如果遇到相应的右括号了就出栈,如果最后栈为空就证明全部有效。
class Solution {
public:
bool isValid(string s) {
stack<char>tmp;
int len=s.size();
if(!len) return true;
if(len%2==1) return false;
for(int i=0;i<len;i++)//continue 必须有 没有的化就得if else否则不会进入下个for循环
{
if(tmp.empty())
{
tmp.push(s[i]);
continue;
}
if(tmp.top()=='('&&s[i]==')')
{
tmp.pop();
continue;
}
else if(tmp.top()=='['&&s[i]==']')
{
tmp.pop();
continue;
}
else if (tmp.top()=='{'&&s[i]=='}')
{
tmp.pop();
continue;
}
tmp.push(s[i]);//如果输入连续的两个左括号没这句是会出错的
}
return tmp.empty();
}
};
题目二:根据每日 气温
列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0
来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
拿到这个题对于菜的不行的我来说首先想到就是最笨的办法。就是拿着数一个一个的去比较。一旦发现一个数大于被比较的数,停止比较,记录下来这个位置,再减去被比较的数的位置就是题目中求的过几天才能升到这个温度,最后一个数不用比较肯定是零。所以首先定义一个和输入数组一样大小的数组(初始化为零)来记录结果。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
vector<int> res(T.size());
for(int i=0;i<T.size()-1;i++)
{
if(T[i]<100)
{
for(int j=i+1;j<T.size();j++)
{
if(T[j]>T[i])
{
res[i]=j-i;
break;
}
}
}
}
return res;
}
};
这个方法确实比较笨但是比较容易想到。耗时也比较长2460毫秒。我们再看一遍程序会发现,我们在拿着数挨个比较的过程中,会有很多的重复,如果我们可以想个办法去掉这些重复就好了,我想过动态规划,双指针(解决数组问题一般就这几个方法)。能实现,但是效果并不好,参考leetcode上的答案。栈确实比较好实现,这个递减栈存放的是数组的索引。为什么说它必须是递减的呢?因为我们目的是找温度高的值离温度低的值有多远。如果温度低于栈顶元素直接将索引入栈代表我们没有找到温度升高的值,如果插入的温度高于栈顶元素证明我们找到了,然后我们开始出栈计算距离
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
stack<int> tmp;
vector<int>res(T.size());
for(int i=0;i<T.size();i++)
{
while(!tmp.empty() && T[i]>T[tmp.top()])
{
res[tmp.top()]=i-tmp.top();//前面比当前数小的温度经过几天才能达到该温度
tmp.pop();//找到经过几天才能到达温度就不需要再找了出栈。
}
tmp.push(i);//把比它小的数pk下去后自己进栈
}
return res;
}
};
算法复杂度是O(n),只用了80多毫秒。速度提升了不少阿。这只是几个数,数越多了提升的效果越明显。
题目三
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
题目三是题目一的扩展,和题目一不同的就是加了数字和字母不再是单纯的去找括号是否匹配,如果没有题目一作为铺垫,可能更容易想到的是递归。有题目一做铺垫,我们可以去构造两个栈,一个存放数字,一个存放字母,然后遍历字符串,遇到 [ 就把数字和字母入栈。代表一个括号内数字和字母组合的开始,遇到] 就把栈顶元素和原来的加(这是精华所在)数字栈和字母栈始终保持只有栈顶元素。
class Solution {
public:
string decodeString(string s) {
stack<string> r;
stack<int> d;
int num=0;
string res;
for(int i=0;i<s.size();i++)
{
if(isalpha(s[i]))
res.push_back(s[i]);
else if(isdigit(s[i]))
{
num=num*10+s[i]-'0'; //需要特别注意的一点减'0'因为是字符串里的值 *10的原因是连续的数字
}
else if(s[i]=='[')
{
r.push(res);
res="";
d.push(num);
num=0;
}
else
{
for(int j=0;j<d.top();j++)
r.top()+=res;
d.pop();
res=r.top();
r.pop();
}
}
return res;
}
};
个人总结:涉及到数组相关需要数组内的数有次序或者需要配对的数组均可以通过单调栈来解决问题。
配对问题: 括号匹配(单纯括号、数字和括号组合)
次序:接雨水 求柱状图中最大矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
这个题的关键就是构建一个递增栈当遇到一个数比栈顶元素小时,就开始出栈,出栈元素是高,宽是当前元素减去出栈元素前一个的索引(因为先出的栈实际减去的是栈顶元素的前一个值多减了所以需要再减去一个)。当插入的元素大于栈中的元素时直接入栈。
class Solution {
public:
int largestRectangleArea(vector<int>& nums) {
stack<int> s;
nums.push_back(0);//是哨兵 即在数组元素最后又加了一个零,对结果没什么影响
int max1=0;
for(int i=0;i<nums.size();++i)
{
while(!s.empty()&&nums[i]<=nums[s.top()])
{
int top=s.top();
s.pop();
max1=max(max1,nums[top]*(s.empty()?i:i-s.top()-1));
}
s.push(i);
}
nums.pop_back();//哨兵循环到最后方便出栈
return max1;
}
};