基础补牢:【https://blog.csdn.net/tham_/article/details/44733101】
根据【http://c.biancheng.net/view/3354.html】,在栈讲义里看到了,链表插入头结点真的是在dummyhead和第一个之间插入的;在队列讲义里看到了双指针…
栈和队列都是【容器适配器】,可以出一道面试题:栈里面的元素在内存中是连续分布的么?
这个问题有两个陷阱:
陷阱1:栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中是不是连续分布不确定。
陷阱2:缺省情况下,默认底层容器是deque,那么deque的在内存中的数据分布是什么样的呢? 答案是:不连续的,下文也会提到deque。
递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中
两种特殊的队列:滑动窗口最大值问题:单调队列 + 求前 K 个高频元素:优先级队列
C++中stack 和 queue 是容器么?
container adapter(容器适配器)
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。
栈和队列的底层实现可以是vector,deque,list 都是可以的,时间复杂度是O( 1 )。
我们使用的stack 和 queue 是属于哪个版本的STL?
SGI STL
我们使用的STL中stack 和 queue 是如何实现的?
默认是以deque实现的
stack 提供迭代器来遍历stack 和 queue 空间么?
否
必须用两个栈才能实现队列;
画图表示【很有必要】!
/** 构造函数,必须有吗?(我知道这个题里有用到但是平时呢) */
//啥也没有,分分号都没有
//while最后记得推进循环进程,多次犯程序空转的错误
//this就是主函数里构造并使用的那个MyQueue吧?
class MyQueue {
public:
MyQueue() {
// shuru;
// shuchu;
}
void push(int x) {
shuru.push(x);
}
int pop() {
if(shuchu.empty()){
while( !shuru.empty()){
shuchu.push(shuru.top());
shuru.pop();
}
}
int res = shuchu.top();
shuchu.pop();
return res;
}
int peek() {
int res = this->pop();//这就是没有参数的时候用this嘛
shuchu.push(res);
return res;
}
bool empty() {
return(shuru.empty() && shuchu.empty());
}
// private:放到公共变量里(并且在最前面),然后构造函数为空(why?因为题目要求的构造函数不需要变量?)
stack<int> shuru;
stack<int> shuchu;
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈,一个输出栈,这里要注意输入栈和输出栈的关系。
*/
队列输入先后:4-3-2-1
back ············· ->························· front
——————————————————
-> 1 ············ 2 ············ 3············ 4·····->
——————————————————
push ············································ pop
queue的常见用法:pop(), push( xxx ), front(), back(), empty();
stack的常见用法:pop(), push( xxx ), top(), empty();
来源[http://c.biancheng.net/view/479.html]
class MyStack {
public:
queue<int> st;
MyStack() {
}
void push(int x) {
st.push(x);
}
int pop() {
///重点是这个函数,要把队列的最后一个元素删掉而不是简单的获取,所以只用back不够
for(int i = 0; i< st.size()-1; i++){
st.push(st.front());
st.pop();
}
int res = st.front();
st.pop();
return res;
}
int top() {
return st.back();
}
bool empty() {
return (st.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();
队列模拟栈,其实一个队列就够了,那么我们先说一说两个队列来实现栈的思路。
队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。
所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用又来备份的!
一个队列在模拟栈弹出元素的时候,只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了。
*/
括号匹配是使用栈解决的经典问题。
后遇到的左括号要先闭合.
【代随】用了纯纯的栈,【力扣】用的栈+哈希表,~~分别默写了一下自己随便改改答案导致爆炸的情况可太多次了orz~
//【1】【代随】解法:栈
//一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
class Solution {
public:
bool isValid(string s) {
stack<int> 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(']');
// 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
// 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
else if (st.empty() || st.top() != s[i]) return false;
else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
}
// 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
return st.empty();
}
};
// // //【2】来源:力扣(LeetCode)
/*为了快速判断括号的类型,我们可以使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。*/
class Solution{
public:
bool isValid(string s){
// int n = s.size();
unordered_map<char, char> pairs = {
{')', '('},
{']', '['},
{'}', '{'}
};
stack<char> stk;
if (! s.size()%2 )return false;
for (int i = 0; i <s.size(); i++){
if (pairs.count(s[i])){
if (stk.empty() || stk.top() != pairs[s[i]]) return false;//stk.empty() ||必须加!!不加有可能移除(空了就没有top了)
else stk.pop();
}else stk.push(s[i]);
}
return stk.empty();
}
};
//【3】自己写的
//char是字符类型, String是字符串类型;String内部用来存储的结果是一个char字符数组。
//stack st 也可以....
//if语句执行行只有1行的话可以不加{},直接跟在if()后面;
class Solution {
public:
bool isValid(string s) {
stack<char> st;
for(int i = 0;i <s.size(); i++ ){
if(s[i]=='[' || s[i]=='{' || s[i]=='(') st.push(s[i]);
else{
if(st.empty()) return false;
else if((s[i]==']' && st.top()=='[')
|| (s[i]=='}' && st.top()=='{')
|| (s[i]==')' && st.top()=='(') ) st.pop();
else return false;
}
}
if(!st.empty()) return false;
return true;
}
};
//由于栈结构的特殊性,非常适合做对称匹配类的题目。
//要写代码之前要分析好有哪几种不匹配的情况,这里有三种不匹配的情况:左多了、右多了、不多不少但不成对(类型不匹配)
匹配问题都是栈的强项
拿字符串直接作为栈,(用字符串模拟栈)这样省去了栈还要转为字符串的操作,但是相应函数就是vector< char >对应的pop_back 和push_back
class Solution {
public:
string removeDuplicates(string s) {
stack<char> st;
st.push(s[0]);
if(s.size()==1) return s;
for(int i = 1; i<s.size(); i++){
if(!st.empty() && s[i]==st.top()) {
st.pop();
continue;
}
else st.push(s[i]);
}
string res;
while(!st.empty()){
res+=st.top();
st.pop();
}
reverse(res.begin(), res.end());
return res;
}
};
在上一题1047.删除字符串中的所有相邻重复项 提到了 递归就是用栈来实现的。
stoi()函数——将数字字符转化位int输出;使用之前要包含头文件#include< string >
!还没搞懂的知识点:
小顶堆
class中class
bool operator()()
priority_queue< 123 >
unordered_map
思路
这道题目主要涉及到如下三块内容:
要统计元素出现频率
对频率排序
找出前K个高频元素
首先统计元素出现的频率,这一类的问题可以使用map来进行统计。
然后是对频率进行排序,这里我们可以使用一种 容器适配器就是优先级队列。
(1)什么是优先级队列呢?
其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
而且优先级队列内部元素是自动依照元素的权值排列。
缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
(2)什么是堆呢?
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。可以把堆分为大顶堆和小顶堆。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素****),小顶堆(堆头是最小元素),如果懒得自己实现的话,就**直接用priority_queue(优先级队列)**就可以实现了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
堆的这种特性非常的有用,堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素。
注意:从栈顶top()处pop
(3)本题我们就要使用优先级队列来对部分频率进行排序。
为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。
此时要思考一下,是使用小顶堆呢,还是大顶堆?有的同学一想,题目要求前 K 个高频元素,那么果断用大顶堆啊。那么问题来了,定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。而且使用大顶堆就要把所有元素都进行排序,那能不能只排序k个元素呢?
所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素(从队头)弹出,最后小顶堆里积累的才是前k个最大元素。
// 链接:https://leetcode.cn/problems/top-k-frequent-elements/solution/c-xiao-bai-you-hao-you-xian-dui-lie-de-j-53ay/
// 无标注版,注意一些pair的使用、priority_queue的使用及其排序结构体的定义
class Solution{
public:
vector<int> topKFrequent(vector<int>& nums, int k){
unordered_map<int,int> map;
for(int i = 0; i< nums.size(); i++) map[nums[i]]++;///默认每个key的val初始是0所以可以直接++
struct myComparison{
bool operator()(pair<int, int>& p1, pair<int, int>& p2) { return p1.second > p2.second; }
// /函数有(),class和struct无()!!!operator() !!!NO second()!!!
};
priority_queue <pair<int,int>, vector<pair<int,int>>, myComparison> q; ///NO myconmarison()
for(auto& a:map){
q.push(a);
if(q.size()>k) q.pop();
}
vector<int> res;
while(!q.empty()){
res.push_back(q.top().first);
q.pop();
}
reverse (res.begin(), res.end());
return res;
}
};
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
//1.map记录元素出现的次数
unordered_map<int,int>map;//两个int分别是元素和出现的次数
for(auto& c:nums){
map[c]++;
}
//2.利用优先队列,将出现次数排序
//自定义优先队列的比较方式,小顶堆;注意其形式不是在小class里就是在小struct里,都是无()有{};
struct myComparison{
bool operator()(pair<int,int>&p1,pair<int,int>&p2){
return p1.second>p2.second;//小顶堆是大于号
}
};
//创建优先队列
priority_queue<pair<int,int>,vector<pair<int,int>>,myComparison> q;
//遍历map中的元素
//1.管他是啥,先入队列,队列会自己排序将他放在合适的位置
//2.若队列元素个数超过k,则将栈顶元素出栈(栈顶元素一定是最小的那个)//并且从栈顶top()处pop,使得最小的被弹出
for(auto& a:map){
q.push(a);
if(q.size()>k){
q.pop();
}
}
//将结果导出,注意要翻转
vector<int>res;
while(!q.empty()){
res.push_back(q.top().first);//emplace
q.pop();
}
reverse(res.begin(), res.end());
return res;
}
};