文章目录
- 引言
 - 复习
 - 前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;
}
 
总结
- 那个优先队列,第一遍写,就没有写对过,还是写错!不过没事,多练练!
 - 优化了差不多两个半小时,学习算法,以后要是笔试没过,得气死,这个投入产出比的效率太低了,还是得严格按照时间要求来做,不然根本跟不上!
 - 服了,服了,今天不该摆烂的,写到差不多半夜,今天的任务才算是完成了,不行呀,项目那里就完成了多机竞争的章节,还不够呀,不行,最迟到下周结束,我得把这个项目搞定,从本周开始,后续都是一天做两道新的题目,然后复习三道题目,不能再花那么多时间了。调整一下!还是得抓住基础!!
 


















