这是我第一次写blog,试一试,想总结一下自己学的东西,也希望自己能坚持下来。
目前打算写一些算法的东西,为明年夏令营的机试做准备。以后如果有空,还会继续写一些计算机专业课(操作系统、网络、数据库等)方面的东西,以及NLP和ML相关的内容。大二学生,自身水平有限,请多多指教。
2019.7.14
qwq 嘻嘻第一次写blog,说在前面,我觉得做算法这玩意儿,每次都是:盯着题目看了一大天,你不一定能想出来。想出来的一般都是BF,时间复杂度都很高,TLE反正是过不了样例。哎,艰难,还有就算写出来了,总有一些奇奇怪怪的样例过不了。所以哎多写代码趴。practice makes perfect
栈:后进先出的线性表(LIFO),递归、逆序
队列:先进先出的线性表(FIFO),缓冲、BFS、层次遍历
堆:优先级队列
单调栈/队列:原理简单,使用较难,很重要
这部分内容没有什么太多的知识,重点在于如何灵活应用
需要掌握基本的STL的用法
1.stack
#include
stack<datatype> s; //create
s.push(x); //push data
datatype y = s.top();//peek value
s.pop(); //delete , no return-value
s.empty(); //return bool
s.size(); //return int
2.queue
#include
queue<datatype> q; //create
q.push(x); //push
datatype head = q.front(); //first data
datatype tail = q.back(); //last data
p.pop(); //delete
p.empty(); //return bool
p.size(); //return int
3.priority_queue
#include
priority_queue<datatype> pq; //create
pq.push(x); //push
datatype y = pq.top(); //first data,only in this way
pq.pop(); //delete
pq.empty(); //return bool
pq.size(); //return int
在c++中,pq默认为最大堆(数值越大,优先级越高)
4.deque
#include
deque<datatype> d;
d.push_back(x);
d.push_front(x);
d.pop_back();
d.pop_front();
x = d.front();
x = d.back();
1.括号匹配
对于给定字符串,判断括号是否匹配
流程: 扫描一遍字符串,如果有左括号就压栈。遇到右括号,检测栈顶,如果匹配就弹出,不匹配则算法结束。最后检测栈是否为空,不为空则说明不匹配。算法实现很简单,注意判断边界条件。
在实现上,其实可以不借助栈,使用变量进行模拟即可。
相关题目:括号匹配 括号分数
2.表达式求值
中缀转后缀
#include
#include
#include
using namespace std;
string test(string str)
{
string ptr = "";
stack<char> s;
for(int i = 0;i < str.size();i++)
{
if(str[i] >= '0'&& str[i] <= '9') //如果是数值,直接输出
{
ptr += str[i];
}
else //如果是操作符
{
if(s.empty()) // (1)栈为空直接压栈
{
s.push(str[i]);
}
else
{
if(str[i] == '(') //(2)左括号直接压栈
s.push(str[i]);
else if(str[i] == ')') //(3)右括号则弹栈至左括号
{
while(s.top() != '(')
{
ptr += s.top();
s.pop();
}
s.pop(); //将左括号弹出
}
//(4)对于运算符,压入栈时,其栈顶的运算符优先级低于自身
else if(str[i] == '+' || str[i] == '-')
{
while(!s.empty() && s.top() != '(')
{
ptr += s.top();
s.pop();
}
s.push(str[i]);
}
else if(str[i] == '*' || str[i] == '/')
{
while(!s.empty() && s.top() != '(' &&
(s.top() == '/' ||s.top() == '*'))
{
ptr += s.top();
s.pop();
}
s.push(str[i]);
}
}
}
}
while(!s.empty()) //如果操作符栈还有操作符
{
ptr += s.top();
s.pop();
}
return ptr;
}
int main()
{
string s = "1+((2+3)*4)-5";
cout<<test(s);
}
3.判断出栈序列合法性
对于给定的入栈序列s1和出栈序列s2,判断s2是否合法
关键在于:出栈序列的元素s2[i]之后比他小的元素,一定是按照降序排列的
我们通过一个栈来模拟,判断序列的合法性
不断将入栈序列元素压入,每次压入一个元素,检查栈顶与指向出栈序列的元素比较,如果相等弹出,指针后又移。最后判断栈是否为空,为空则合法
bool test(string s1,string s2)
{
if(s1.size() != s2.size())
return false;
stack<char> s;
int top = 0;
for(int i = 0;i < s1.size();i++)
{
s.push(s1[i]);
while(!s.empty() && s.top() == s2[top])
{
s.pop();
top++;
}
}
if(!s.empty())
return false;
return true;
}
主要是在BFS的时候用得多一点
顾名思义,栈中的元素是单调的
单调递增/减栈:从栈底向上元素单调递增/减
用途:寻找比当前元素下一个更小/大的前后元素以及其位置
void test(vector<int>&num) //实现一个严格单调递增栈
{
stack<int> s;
for(int i = 0;i < num.size();i++)
{
while(!s.empty()&&s.top() > num[i])
s.pop();
s.push(num[i]);
}
}
1.bad hair day
题目大意:对于数组每一个元素,计算其到右边第一个比他大的元素的步数-1
使用暴力搜索的时间复杂度:O(n2)
int test(vector<int>&num) //O(n)
{
stack<int> s;
int sum = 0;
for(int i = 0;i < num.size();i++)
{
while(!s.empty() && num[s.top()] <= num[i]) //构造一个单调递减栈
s.pop();
sum += s.size();
s.push(i);
}
return sum;
}
2.next greater number 1
vector<int> test(vector<int>&num1,vector<int>&num2)
{
stack<int> s;
vector<int> index;
if(num2.size() == 0)
return index;
map<int,int> p; //value--index
for(int i = 0;i < num1.size();i++)
{
while(!s.empty() && num1[s.top()] < num1[i]) //单调递减栈
{
p[num1[s.top()]] = i; //栈顶遇到第一个比他大的元素num1[i]
s.pop();
}
s.push(i);
}
for(int i = 0;i < num2.size();i++)
index.push_back(p[num2[i]] == 0 ? -1 :num1[p[num2[i]]]);
return index;
}
/*执行用时 :8 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗 :9.5 MB, 在所有 C++ 提交中击败了35.96%的用户*/
next greater number 2
题目变化在于是循环数组,只需要将数组放大一倍即可
3.largest rectangle in histogram
题目大意:对于直方图,找出最大矩形面积
实际上,我们需要找到元素的第一个比他小的值
int test(vector<int>&num)
{
int ans = 0;
stack<int> s;
num.push_back(0); //这里有个技巧,在数组末尾加入0是为了最后清空栈
for(int i = 0;i < num.size();i++)
{
while(!s.empty()&&num[s.top()] > num[i]) //严格单调递增
{
//弹出一个元素,计算以该元素为基准的max值
int bar = num[s.top()];
s.pop();
//如果弹出后栈为空,说明该bar可以左扩张到左边界
//如果弹出后栈不为空,说明该元素是目前区间最大的,
//不能再继续左扩展
//弹栈元素的最大右扩张值是当前while第一个弹出的bar
int index = s.empty() ? -1 : s.top();
ans = max(ans,bar * (i - index - 1));
}
s.push(i);
}
return ans;
}
/*执行用时 :12 ms, 在所有 C++ 提交中击败了96.98%的用户
内存消耗 :10.6 MB, 在所有 C++ 提交中击败了35.50%的用户*/
4.maximal rectangle
这道题和上一个类似,只需要计算出每一个数组的高度,然后调用上面的函数即可
入队:这个和单调栈差不多,为了维护单调性,如果队尾元素大于将要入队的元素,则队尾元素出队,一直到队尾元素小于/大于将要入队的元素。
出队:被动出队(为了维护单调性,从队尾出队)、主动出队(和普通队列一样)
void test(vector<int>&num)
{
//实现一个严格单调递增队列
deque<int> d;
for(int i = 0;i < num.size();i++)
{
while(!d.empty() && num[d.back()] > num[i])//被动出队
d.pop_back();
d.push_back(i); //入队
}
}
1.sliding windows
题目大意:给定一个数组和一个窗口尺寸k,让这个窗口在数组上移动(从0开始),每移动一个元素得到一个子数组,求每个子数组的最大值、最小值.
暴力搜索:O(nk),采用单调队列:O(n)
运行流程
对于数组:1,3,-1,-3,5,3,6,7 设窗口尺寸k=3,来计算最小值
(1)1入队,[1],min[0] = 1
(2)3入队,满足单调递增,[1,3],min[1] = 1
(3)-1入队,弹出1,3,[-1],min[2] = -1
(4)-3入队,弹出-1,[-3],min[3] = -3
(5)5入队,满足 单调递增,[-3,5],min[4] = -3
(6)3入队,弹出5,[-3,3],min[5] = -3
(7)6入队,满足单调递增,[-3,3,6],i - d.front() + 1 > k,-3出队,[3,6],min[6] = 3
(8)7入队,满足单调递增,[3,6,7],min[7]=3
最后min[k-1:n-1] = [-1,-3,-3,-3,3,3]
void test(vector<int>&num,int k)
{
deque<int> d;
vector<int> max,min;
for(int i = 0;i < num.size();i++)
{
//构造一个单调递增队列
while(!d.empty() && num[d.back()] > num[i]) //不满足单调性出队
d.pop_back();
d.push_back(i);
while(i - d.front() + 1 > k) //大于窗口尺寸
d.pop_front();
min.push_back(num[d.front()]);
}
d.clear();
for(int i = 0;i < num.size();i++)
{
while(!d.empty() && num[d.back()] < num[i])
d.pop_back();
d.push_back(i);
while(i - d.front() + 1 > k)
d.pop_front();
max.push_back(num[d.front()]);
}
for(int i = k-1 ;i < num.size();i++)
{
cout<<min[i]<<" ";
}
cout<<endl;
for(int i = k-1 ;i < num.size();i++)
{
cout<<max[i]<<" ";
}
}
单调队列最主要的应用(我的理解):在区间中求最值
单调队列还可以应用于DP进行优化
sliding Windows problem set
未完待续。。。