文章目录
- 引言
- 复习
- 前K个高频元素——使用堆去做
- 个人实现
- 参考官方——使用堆实现
- 定义优先队列的基本方式
- 新作
- 数据流的中位数
- 个人实现
- 参考做法
- 有效括号
- 个人实现
- 参考实现
- 最小栈
- 个人实现
- 参考实现
- 字符串解码
- 个人实现
- 参考实现
- 总结
引言
- 差不多摆烂了一上午,本来今天周六,啥也不想干,上午补了这一周的觉,醒来之后的还是有点难受的,感觉浪费了一上午,然后中午《凡人修仙传》一看,我就彻底陷入了摆烂,啥也不想干。逛逛B站,本来想十二点就结束的,结果又看了一个动漫解说,一下子看到了一点钟,才开始今天的刷题。
- 中间再找明天去聚会的饭店,发现自己的真的没有钱呀,人均一百多的小龙虾,舍不得吃,甚至都不想出去吃了,感觉在家吃会更便宜。现在经济实力不行,整个人整的状态都不好了,很难受!早点工作吧,手里有钱,也不会像现在过的那么难受和局促,还是得找一个薪水丰厚的工作。
- 加油吧,兄弟,继续准备秋招吧!
- 今天把昨天的哪个算法,有一个官方解法——堆的解法没有做过。
复习
前K个高频元素——使用堆去做
- 第一次做的链接
- 题目链接
个人实现
- 这里需要实现两种方法,第一种是自己最初使用不同数据结构实现的,第二种是使用计数排序实现的。
常规数据结构排序实现
- 顺利实现,没啥问题,还行!
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 计算每一个元素出现的频率
unordered_map<int,int> c;
for(auto x :nums) c[x] ++;
// 然后按照的元素的出现的频率进行排序
vector<pair<int,int>> p;
for(auto [k,v]:c) p.push_back({k,v});
sort(p.begin(),p.end(),[](auto a,auto b){
return a.second > b.second;
});
// 这里怎么去除对应的元素并不知道的
// 返回最初的几个元素
vector<int> res;
for(int i = 0;i < k;i ++) res.push_back(p[i].first);
return res;
}
};
使用计数排序实现
- 通过计数排序,返回最先的几个元素
- 其实我觉得自己写的有点繁琐,并不如昨天哪个代码一样简洁
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
int m = nums.size();
// 计算每一个元素出现的频率
unordered_map<int,int> c;
for(auto x :nums) c[x] ++;
// 记录所有的元素出现的排序情况的
vector<int> p(m + 1);
for(auto [k,v]:c) p[v] ++;
// 定义edge边界,大于这个边界的就是前k个,默认是最大的
int edg = m ;
// 定义x用来记录前几,和k进行比较
int x = 0;
while(x < k) x += p[edg--] ;
// 遍历字典元素,输出判断
vector<int> res;
for(auto [k,v] : c) if(v > edg) res.push_back(k);
return res;
}
};
参考官方——使用堆实现
-
这里重新复习一下,单纯是为了补充一下这个专题的知识,并不会使用堆实现对应的方法,除了知道调用优先队列实现底层是使用堆的,其他都不知道。
-
这里的思路之前都是一样的,不过最后对出现次序进行排序,是通过对排序实现的,具体思想如下
- 如果堆的元素小于k,就可以直接插入堆
- 如果堆的元素个数等于k,检查堆顶元素和当前出现次数的大小(小顶堆)
- 堆顶更大,至少有个k个数字的出现次数比当前值大,故舍弃当前值
- 堆顶更小,弹出堆顶,并将当前元素插入堆中
这里是使用C++中的priority_queue来实现的,基本上前面是一致的,然后后续加了一个优先队列堆排序
class Solution {
public:
struct cmp{
bool operator()(pair<int,int> a,pair<int,int> b){
return a.second > b.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
int m = nums.size();
// 计算每一个元素出现的频率
unordered_map<int,int> c;
for(auto x :nums) c[x] ++;
// 记录所有的元素出现的排序情况的
// 这里使用优先队列实现
priority_queue<pair<int,int>,vector<pair<int,int>>,cmp> f;
for(auto [x,y] : c){
if(f.size() == k){
// 比较堆顶元素,判定谁大
if(f.top().second < y) {
f.pop();
f.push({x,y});
}
}else{
f.push({x,y});
}
}
// 遍历字典元素,输出判断
vector<int> res;
while(!f.empty()) res.push_back(f.top().first),f.pop();
return res;
}
};
定义优先队列的基本方式
- 这里没有记住,应该好好复习一下,本来想这样想的,但是不会写,换成了vector。
- 这里再补充一下
- 这里有几个地方需要注意一下,
- 就是这里是oprator()操作对象,带上括号才是一个函数名,一定不要忘记加上括号
- 泛型编程声明的时候,就要传入对应的底层容器和比较方法
#include <queue>
using namespace std;
struct comp{
// bool coprator(pair<int,int> a,pair<int,int >b){
// 上面写错了,operator()是一个成员函数,是一个函数名
bool operator()(pair<int,int> a,pair<int,int >b){
return a.second > b.second;
}
}
int main(){
// priority_queue<pair<int,int>> q(pair<int,int>,vector<pair<int,int>>,comp);
// 写错了,在指定泛型编程的时候,就要说明是什么样的中间容器以及是用什么样的排序函数
priority_queue<pair<int,int>,vector<pair<int,int>>,comp> f;
}
新作
数据流的中位数
- 这道题是hard,有点难搞!
题目链接
注意 - 偶数取平均,奇数取中间
- 这个题目主要有三个部分
- MedianFinder是初始化函数,初始化一个序列
- addNum是将给的列表中的数据,添加到数据结构中
- findMedian是返回目前所有元素的中位数
- 中位数的精度要保存超过 1 0 − 5 10^{-5} 10−5,最起码保存5位小数
个人实现
- 这道题代码量蛮多的,要实现的内容很多,和之前的LRU很像,思路并不能,因为找中位数就那几种方法,而且这里是主键添加新的元素,并不保证是否是按照递增的顺序增加的元素,所以需要每一次添加都排序,找到最先插入的位置,根据插入位置更新原先中位数的坐标。
- 两个操作
- addNum:找到需要插入的位置,并根据插入位置更新中位数的坐标
- findNum:单纯返回目标值
- MedianFinder:明确需要创建的对象,midl和midr都是保存的中间值的索引
- vector保存数组
其实这里使用双向链表,然后在进行快排的效果会更好的,按时不影响,没有对时间复杂度做出要求,不影响
具体实现如下
class MedianFinder {
public:
vector<int> seq;
int midL,midR ;
MedianFinder() {
midL = 0;
midR = 0;
}
void addNum(int num) {
// 直接添加元素
seq.push_back(num);
sort(seq.begin(),seq.end());
midR = seq.size() / 2;
midL = midR - 1;
}
double findMedian() {
if(seq.size() % 2){
// 奇数
return (float)seq[midR];
}else{
return (float)(seq[midL] + seq[midR]) / (float)2 ;
}
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
- 超时了,正常不应该进行排序的,这里还是使用优先队列堆,进行排序才行,这样的时间复杂度才是最低的。
- 但是用堆的话,没有办法进行完整地便利,进而不能确定中间值,是一个问题
- 这题应该是要求你手动进行的堆排序,然后每次插入只需要进行堆排序就行了,现在我是记得快速排序的模板的
- 超时了,就这样吧
参考做法
这里是使用了对顶堆的方式来实现的,果真是的,堆不会实现,然后这道题就卡在这里了。不过如果是这样的话,不就是将问题拆解成两个都堆,就不需要具体实现对应的堆了。
实现代码
class MedianFinder {
public:
// 分别创建大顶堆和小顶堆负责左右两边的序列
priority_queue<int,vector<int>,greater<int>> up;
priority_queue<int> down;
MedianFinder() {
}
void addNum(int num) {
// 优先插入大顶堆,负责左边的
// 新插入的元素小于大顶堆的队首元素,也就是最大值
if(down.empty() || num <= down.top()){
down.push(num);
// 插入之后判定两个堆顶的元素数量是否符合要求
if(down.size() > up.size() + 1){
up.push(down.top());
down.pop();
}
}else{
up.push(num);
if(down.size() < up.size()){
down.push(up.top());
up.pop();
}
}
}
double findMedian() {
if((down.size() + up.size()) % 2) return down.top();
else
return (down.top() + up.top()) / 2.0 ;
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
总结
- 后面有点燥了,没看完,直接学写他的代码,没有自己按照这个思路写,明天再来吧,两道题整的有点烦躁的。
有效括号
题目链接
注意
- 仅仅只有三个类型的括号
- 长度是从1到10的4次方
- 左右必须按照顺序进行拟合
个人实现
- 这道题第一次做过了,我记得当时好像有两个很重要的推论,但是没记住,想想看
- 在一个括号序列中,任意前缀的左括号的数量大于等于右括号数量
- 左右括号的数量相等
具体实现
class Solution {
public:
bool isValid(string s) {
stack<int> t;
for(auto x:s){
if(x =='[' || x =='{' || x == '(') t.push(x);
else{
if(t.empty() || abs(x - t.top()) > 2) return false;
t.pop();
}
}
if(!t.empty()) return false;
return true;
}
};
参考实现
- 这里的参考实现参见之前文章,具体链接
最小栈
题目链接
注意
- 每一个操作最多调用有限制
个人实现
- 这里如果单纯使用已经有的栈来实现,并没有办法获取整个栈中最大的元素,因为没有办法获取栈中的元素的具体的值。
- 如果这里使用一个有序的数据结构,并不好实现,因为会出栈,就需要删除特定的元素,这里就需要重新进行排序,这是完全没有必要的。
- 因为出栈和入栈是一种状态变化,所以可以使用一个数组来记录每一个状态的最值,然后入栈就更新对应的最值,出栈就是延续上一个状态的最值,然后获取数据集就是返回当前的数组值。
具体实现
- 如果是pop,就将idx–,说明最小值是上一个元素。
class MinStack {
public:
stack<int> s;
int minValue = INT_MIN,idx = 0;
vector<int> f;
MinStack():minValue(INT_MIN),idx(0),f(30000,INT_MAX) {
}
void push(int val) {
// 更新并记录一下最值
if(idx == 0) f[idx ++] = val;
else{
f[idx] = min(f[idx - 1],val);
idx ++;
}
// 将对应元素加入到栈中
s.push(val);
}
void pop() {
idx --;
s.pop();
}
int top() {
return s.top();
}
int getMin() {
return f[idx-1];
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
- 虽然是过了,但是超时了,正常不应该花费这么多时间的,应该很快就做出来的,但是有几个地方自己弄混了。pop就是上一个状态的最大值,还是得拿纸币画一下
单调栈
参考实现
- 大概思路相同,但是有两个地方我没想到
- 维持的一个前缀和最小值,实际上也是一个栈,我这里是完全可以改成栈的操作的
- 然后这里做了一个优化,很好理解,但是不好证明,在做这道题的时候,不用优化也行,这里就不写了。
class MinStack {
public:
stack<int> s,f;
MinStack() {
}
void push(int val) {
// 更新并记录一下最值
if(f.empty() || f.top() >= val) f.push(val);
// 将对应元素加入到栈中
s.push(val);
}
void pop() {
if(s.top() <= f.top()) f.pop();
s.pop();
}
int top() {
return s.top();
}
int getMin() {
return f.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
确实快不少
字符串解码
题目链接
注意
- 输入是一个有效的数字
- 输入都是小写字母加数字,字母是重复的对象,数字是重复的个数
个人实现
- 这道题就是一个单纯的回溯呀,使用单纯使用栈操作会好很多。
- 想想看怎么模拟哈
- 一定要有一个模拟的字符str;
- 递归函数返回的是拼接好之后的字符串
- 终止条件?
- 左边等于右边,然后结束?想想看哈!
这里就是使用两个栈来实现的,但是有点麻烦,整了差不多二十分钟,就写成了这样,还有一半样例过不去
class Solution {
public:
// 字符串重复拼接任务
string repitStr(int k,string str){
string res = "";
for(int i = 0;i < k;i ++) res += str;
return res;
}
string decodeString(string s) {
string res;
// 如何拆解括号?使用两个栈实现递归
int m = s.size();
stack<int> nums;
stack<string> strs;
for(int i = 0;i < m;){
// 判定为字符串
if(s[i] >= '1' && s[i] <= '9'){
int temp = s[i ++] - '1' + 1;
// 确定为数字,并进行拼接
while(s[i] >= '0' && s[i] <= '9') s[i ++] - '1' + 1;;
nums.push(temp);
// cout<<temp<<endl;
}
else if(s[i] >= 'a' && s[i] <= 'z'){
string temp;
while(s[i] >= 'a' && s[i] <= 'z') temp += s[i ++];
strs.push(temp);
// cout<<temp<<endl;
}else if(s[i] == '['){
// 左括号,直接跳过
i ++;
}else{
// 如果是右括号,直接执行操作
// 如果两者都不为空
int k = nums.top();
string strTemp = strs.top();
nums.pop();
strs.pop();
string temp = repitStr(k,strTemp);
if(nums.empty()) res += temp;
else strs.push(temp);
cout<<repitStr(k,strTemp)<<endl;
i++;
}
}
if(!strs.empty()) res+= strs.top();
return res;
}
};
不过知道怎么修改了,我改试试看,不能无休止的花时间
- 这里应该是用括号压栈和弹出的思路去做,不然太费劲了
class Solution {
public:
// 字符串重复拼接任务
string repitStr(int k,string str){
string res = "";
for(int i = 0;i < k;i ++) res += str;
return res;
}
string decodeString(string s) {
string res;
// 如何拆解括号?使用两个栈实现递归
int m = s.size();
stack<int> nums;
stack<string> strs;
for(int i = 0;i < m;){
// 判定为字符串
if(s[i] >= '1' && s[i] <= '9'){
int temp = s[i ++] - '0' ;
// 确定为数字,并进行拼接
while(s[i] >= '0' && s[i] <= '9') temp = temp * 10 + (s[i ++] - '0') ;
nums.push(temp);
cout<<temp<<endl;
}
else if(s[i] >= 'a' && s[i] <= 'z'){
string temp;
while(s[i] >= 'a' && s[i] <= 'z') temp += s[i ++];
if(nums.empty()) res += temp;
else strs.push(temp);
cout<<temp<<endl;
}else if(s[i] == '['){
strs.push("[");
// 左括号,直接跳过
i ++;
}else{
// 如果是右括号,直接执行操作
int k = nums.top();
nums.pop();
// 这里要不断弹出字符直到遇到[
string temp = "";
while(strs.top() != "["){
temp = strs.top() + temp;
strs.pop();
}
temp = repitStr(k,temp);
strs.pop();
if(nums.empty()) res += temp;
else
strs.push(temp);
cout<<temp<<endl;
i++;
}
}
return res;
}
};
总结
- 真的是把我自己笑死了,这个问题的明明很简单,但是整整花了差不多一个小时,不断调整bug,一开始跳过了括号,然后又发现开头和结尾没有处理好,然后又发现数字的计算没有处理好,一点点补充。
数字的计算
if(s[i] >= '1' && s[i] <= '9'){
int temp = s[i ++] - '0' ;
// 确定为数字,并进行拼接
while(s[i] >= '0' && s[i] <= '9') temp = temp * 10 + (s[i ++] - '0') ;
- 看看你上面,脑子抽风,在写什么?
参考实现
- 首先明确格式,这是一个递归,无论是外面,还是里面都是一样的,递归的形式就是k[string],就是这样的形式
- 想的真好呀,就是找下一个目标字符,我这里就是太费劲了。
- 大概写一下吧,搞不动了!
string decodeString(string s){
int u = 0;
return dfs(s,u);
}
string dfs(string& s,int& u){
string res;
while(u < s.size() && s[u] != ']'){
// 一开始是字符的话,直接 加到结果上去
if(s[u] >= 'a' && s[u] <= 'z') res += s[u++];
// 组装数字
else if(s[u] >= '0' && s[u] <= '9'){
int k = u;
while(s[u] >= '0' && s[u] <= '9') k ++;
u = k + 1;
string y = dfs(s,u);
u ++;
while(x --) res += y;
}
}
return res;
}
总结
- 那个优先队列,第一遍写,就没有写对过,还是写错!不过没事,多练练!
- 优化了差不多两个半小时,学习算法,以后要是笔试没过,得气死,这个投入产出比的效率太低了,还是得严格按照时间要求来做,不然根本跟不上!
- 服了,服了,今天不该摆烂的,写到差不多半夜,今天的任务才算是完成了,不行呀,项目那里就完成了多机竞争的章节,还不够呀,不行,最迟到下周结束,我得把这个项目搞定,从本周开始,后续都是一天做两道新的题目,然后复习三道题目,不能再花那么多时间了。调整一下!还是得抓住基础!!