新知识获取
C++标准库是有多个版本的,要知道我们使用的STL是哪个版本,才能知道对应的栈和队列的实现原理。
那么来介绍一下,三个最为普遍的STL版本:
HP STL 其他版本的C++ STL,一般是以HP STL为蓝本实现出来的,HP STL是C++ STL的第一个实现版本,而且开放源代码。
P.J.Plauger STL 由P.J.Plauger参照HP STL实现出来的,被Visual C++编译器所采用,不是开源的。
SGI STL 由Silicon Graphics Computer Systems公司参照HP STL实现,被Linux的C++编译器GCC所采用,SGI STL是开源软件,源码可读性甚高。
主要收获:STL的源码在网上是开源的,可以使用这个源码去学习C++的写库的一些特性,学习专业的C++编程技巧。
1.用栈实现队列
学到的点:
1.stack的初始化,以及如何获取弹出的元素,先用top(),再用pop().
2.又发现了一个自己疏忽的点:for循环中定义的临时变量,for循环结束后便会被释放;同理,在{}里面定义的局部变量也会随着{}的结束而被释放。
3.代码随想录中的拓展部分给出一些建议与告诫,这些告诫非常实用。
class myQueue {
public:
stack<int> stIn;
stack<int> stOut;
//构造函数
myQueue() {};
void push(int i) {
stIn.push(i);
}
int pop() {
//先按照自己的思路写伪代码
/*
1.检查输出栈是否为空
2.若为空,则将输入中的元素弹出压入输出栈
2.若不为空,则输出栈直接pop就行
*/
int result;
if (!stOut.empty()) {
result = stOut.top();
stOut.pop();
}
else {
while (!stIn.empty()) {
stOut.push(stIn.top());
stIn.pop();
}
result = stOut.top();
stOut.pop();
}
return result;
}
int peek() {
int result = this->pop();
stOut.push(result);
return result;
}
/* int peek() {
int result;
if (!stOut.empty()) {
result = stOut.top();
}
else {
while (!stIn.empty()) {
stOut.push(stIn.top());
stIn.pop();
}
result = stOut.top();
}
return result;
}
*/
bool empty() {
return stIn.empty() && stOut.empty();
}
};
2.用队列实现栈
思路还算简单,一个队列足够了。这道题我学到了queue的push()方法,front(), pop()方法,而且队列之间是可以直接赋值的,如que1 = que2。这道题做完,对于队列和栈的先入先出,后入后出理解更深了。
我的方法:
class MyStack {
public:
queue<int> queue1;
queue<int> queue2;
MyStack() {
}
void push(int x) {
queue1.push(x);
}
int pop() {
int result{0}, temp{ 0 }, num{ 0 };
while (!queue1.empty()) { //若非空
temp = queue1.front();
queue2.push(temp);
queue1.pop();
num++;
}
result = temp;
num = num - 1;
while (num--) { //若非空
queue1.push(queue2.front());
queue2.pop();
}
queue2.pop();
return result;
}
int top() {
//服用pop
int result = this->pop();
queue1.push(result);
return result;
}
bool empty() {
return queue1.empty();
}
};
使用queue的size方法,计数更方便。
class MyStack {
public:
queue<int> queue1;
queue<int> queue2;
MyStack() {
}
void push(int x) {
queue1.push(x);
}
int pop() {
int result;
int size = queue1.size() - 1;
while (size--) { //若非空
queue2.push(queue1.front());
queue1.pop();
}
result = queue1.front();
queue1.pop();
size = queue2.size();
while (size--) { //若非空
queue1.push(queue2.front());
queue2.pop();
}
/*
que1 = que2; // 再将que2赋值给que1
while (!que2.empty()) { // 清空que2
que2.pop();
}
*/
return result;
}
int top() {
//服用pop
int result = this->pop();
queue1.push(result);
return result;
}
bool empty() {
return queue1.empty();
}
};
代码随想录优化:仅用一个队列即可实现
class MyStack {
public:
queue<int> queue1;
MyStack() {
}
void push(int x) {
queue1.push(x);
}
int pop() {
int result;
int size = queue1.size() - 1;
while (size--) { //若非空
queue1.push(queue1.front());
queue1.pop();
}
result = queue1.front();
queue1.pop();
return result;
}
int top() {
//服用pop
int result = this->pop();
queue1.push(result);
return result;
}
bool empty() {
return queue1.empty();
}
};
3.有效的括号
这里用的是栈的知识。
代码随想录中这部分提醒的非常好,自己做题目总是不把细节理清楚就去开始写,结果报错非常多。
代码随想录里的想法就更简洁了,s[i] =‘{’,那么直接压入‘}’就可以了,之后遇到新字符直接看相不相等就好了。比我的写法简洁太多了。
注意点:当stack为空时,调用top()方法会报错,(temp.empty()||s[i] != temp.top()) 如果没有考虑最开始的奇数情况时,temp.empty()必须放置在||之前,||和&&是短路运算符,这很重要。
我的思路:
class Solution {
public:
bool isValid(string s) {
int len = s.size();
stack<char> temp;
for (int i = 0; i < len; i++) {
if (temp.empty() && (s[i]==')' ||s[i]==']' ||s[i]=='}') ) {
return false;
}
if (s[i] == '(' || s[i] == '[' || s[i] == '{') {
temp.push(s[i]);
}
else {
if(s[i] == '}'&&temp.top() == '{' || s[i] == ']'&&temp.top() == '[' || s[i] == ')'&&temp.top() == '(') {
temp.pop();
}
else {
return false;
}
}
}
return temp.empty();
}
};
代码随想录的想法:
class Solution {
public:
bool isValid(string s) {
int len = s.size();
if(len % 2 != 0) {
return false;
}//特殊情况考虑
stack<char> temp;
for (int i = 0; i < len; i++) {
if (s[i] == '(') {
temp.push(')');
}
else if (s[i] == '[') {
temp.push(']');
}
else if (s[i] == '{') {
temp.push('}');
}
else if (temp.empty()||s[i] != temp.top()) {
return false;
}
else {
temp.pop();
}
}
return temp.empty();
}
};
4. 删除字符串中所有相邻重复项
比较基础的栈的用法。
比较妙的点:
1.我的方法中用了两个栈,但是字符串的反转操作是可以直接替代掉一个栈的,我对C++ stl以及string的使用还是太生疏了。
2.代码随想录中将字符串作为栈,利用了string类型的pop_back方法,push_back方法,back(返回最后一个字符)方法,front方法(返回第一个字符),之前还用过size方法,resize方法。代码随想录中极为精简的方法很值得学习。
我的方法:
class Solution {
public:
string removeDuplicates(string s) {
int len = s.size();
stack<char> stack1;
stack<char> stack2;
for (int i = 0; i < len; i++) {
if (stack1.empty()||s[i] != stack1.top()) {
stack1.push(s[i]);
}
else {
stack1.pop();
}
}
//接下来是弹出操作
while (!stack1.empty()) {
stack2.push(stack1.top());
stack1.pop();
}
string result;
while (!stack2.empty()) {
result.push_back(stack2.top());
stack2.pop();
}
return result;
}
};
代码随想录的巨精简写法:
class Solution {
public:
string removeDuplicates(string s) {
int len = s.size();
string result;
for (char str:s) {
if (result.empty() || str != result.back()) {
result.push_back(str);
}
else {
result.pop_back();
}
}
return result;
}
};
5.逆波兰表达式求值:
新知识点:将字符串转变为对应整数值的数值用stoi(转化去整数)stii=oo(转化为longlong类型的名字)
//逆波兰表达式求值:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
int len = tokens.size();//获取字符串的长度
stack<int> stack_use;
for (int i = 0; i < len; i++) {
//默认表达式都是正确的表示方法,否则报错
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
int nums1 = stack_use.top();
stack_use.pop();
int nums2 = stack_use.top();
stack_use.pop();
if (tokens[i] == "+") {
stack_use.push(nums2 + nums1);
}
else if (tokens[i] == "-") {
stack_use.push(nums2 - nums1);
}
else if (tokens[i] == "*") {
stack_use.push(nums2 * nums1);
}
else if (tokens[i] == "/") {
stack_use.push(nums2 / nums1);
}
}
else {
stack_use.push(stoi(tokens[i]));
}
}
return stack_use.top();
}
};
6.滑动窗口中的最大值
1.这道题是完全跟着代码随想录中的思路,我自己理解的不是很好,后期需要反复来研究。
2.deque是一个可以在两端任意进行操作的队列,这个队列的使用在这道题中有体现。
3.对于如何向vector中添加元素,使用push_back在这道题中也有体现。
第二日重新理解:
class myQuene{
public:
deque<int> que; // .front()返回第一个元素; .back() 返回最后一个元素 deque是一个可以在两端任意进行操作的队列。
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
int front() {
return que.front();//返回deque的首个元素,是当前滑动窗口的最大值。
}
};
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
//代码随想录中给出的是用单调队列的方法
myQuene temp;
vector<int> result;
int len = nums.size();
for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
temp.push(nums[i]);
}
result.push_back(temp.front()); // result 记录前k的元素的最大值
for (int i = k; i < len; i++) {
temp.pop(nums[i - k]); // 滑动窗口移除最前面元素
temp.push(nums[i]); // 滑动窗口前加入最后面的元素
result.push_back(temp.front()); // 记录对应的最大值
}
return result;
}
};
我的第二天复习:待解释代码:
```cpp
class myQuene {
public:
deque<int> que;
void pop(int value) {//弹出元素:当滑动窗口中的首个元素与单调队列中首个元素相同时,滑动窗口时,队列中的首个元素应该被弹出
if (!que.empty() && que.front() == value) {
que.pop_front();
}
}
void push(int value) {
while(!que.empty() && value > que.back()) { //构建递减单调队列
que.pop_back();
}
que.push_back(value);
}
int front() { //查看当前窗口最大值
return que.front();
};
};
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
//代码随想录中给出的是用单调队列的方法
myQuene temp;
int len = nums.size();
vector<int> result;
for (int i = 0; i < k; i++) {
temp.push(nums[i]);
} //首先维护第一个窗口
result.push_back(temp.front());//将第一个窗口最大值存入result.
for (int i = k; i < len; i++) {
temp.pop(nums[i-k]);
temp.push(nums[i]);//不断移动滑动窗口,在这个过程中删除滑动窗口中第一个元素以及满足条件的单调队列中的该元素
//(条件即为滑动窗口中第一个元素与单调队列中第一个元素相等时)
result.push_back(temp.front());
}
return result;
}
};
8.前K个高频元素
这道题相对比较麻烦了,应该是目前遇到最难的,涉及到了二叉树数据结构方面的知识,以及stl中的各种数据结构知识,知识量交错复杂。我之前都没怎么认真分析复杂度,后面要特别关注一下这一点。
该题代码用的是代码随想录中的C++示例代码,我的基础不太扎实,这块很多数据结构没用过,stl熟悉度低。
这个题的思路是:
1.利用map统计元素频率。
2.利用小顶堆做频率排序。
3.取出K个高频元素。
剩下一天的要求:
1.看一下stl中优先级队列的源码,map数据结构的源码,pair数据结构的源码。
2.二叉树的基本概念掌握一下。
class Solution {
public:
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
//operator是运算符重载关键词,这里面是将()重载为一个函数,具体使用方法就是:
/*
* mycomparison tempcase;
* bool result = tempcase(pair1,pair2);
*/
vector<int> topKFrequent(vector<int>& nums, int k) {
//对元素频率进行统计
unordered_map<int, int> map; //首先定义一个map数据结构,key和value的类型都为int
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}//字典形式构建好了(元素频率统计成功了)
//用小顶堆去做频率排序
//定义一个小顶堆,大小为k;大顶堆和小顶堆内部都是二叉树,大顶堆是二叉树顶部的元素最大,小顶堆是二叉树顶部的元素最小。
//pair<int, int> 是一个标准库类型,表示由两个整数组成的键值对。这里用作比较两个键值对的第二个元素。
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que; //代码随想录说此时已经是一个最小堆了,我还不是很理解,应该是stl底层库的原因
//pair<int, int>为存储的数据类型,vector<pair<int, int>>底层存储使用的数据类型,mycomparison为元素的比较方式。
//定义一个优先级队列,这里有三个模板参数
/*
priority_queue 可以通过三个模板参数进行定制:
T:存储的数据类型。
Container(可选):底层存储使用的容器类型,默认是 std::vector<T>。
Compare(可选):元素的比较方式,默认是 std::less<T>,表示最大堆。
*/
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) { //这种遍历迭代的方法特别第一次见
pri_que.push(*it);//*it为解引用迭代器,第一次见,stl库要常用
if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
pri_que.pop();//因为是最小堆,所以弹出的是最小值
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);//k指定了vector数组的长度
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};