2021年6月/7月

2021-7-19

  • 全手工实现LRU(Least Recently Used)算法
#include <iostream>
#include <map>

using namespace std;

struct Node {
    int key, val;
    Node *pre, *next;

    Node() : pre(NULL), next(NULL) {}

    Node(int k, int v) : key(k), val(v) {}
};

class DoubleList {
public:
    //构造
    DoubleList() {
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head->next = tail;
        tail->pre = head;
        size = 0;
    }

    //在链表尾部添加元素,时间复杂度O(1)
    void addLast(Node *x) {
        tail->pre->next = x;
        x->pre = tail->pre;
        x->next = tail;
        tail->pre = x;
        size++;
    }

    //删除链表中的x节点(该节点一定存在)节点指针(地址)由哈希表给出
    void remove(Node *x) {
        x->pre->next = x->next;
        x->next->pre = x->pre;
        //free(x);  此处不能free,因为挪动节点到开头需要用到此函数
        size--;
    }

    //删除链表中的第一个节点,并返回该节点,时间O(1)
    Node *removeFirst() {
        if (head->next == tail)
            return NULL;
        Node *first = head->next;
        head->next = first->next;
        head->next->pre = head;
        size--;
        return first;
    }

    int getSize() {
        return size;
    }
    //按照LRU顺序,展示所有键值,用于测试
    void show(){
        Node*p=tail;
        while(p->pre!=head){
            cout<<p->pre->key<<":"<<p->pre->val<<" ";
            p=p->pre;
        }
    }

private:
    //头尾虚节点
    Node *head, *tail;
    //链表元素个数
    int size;

};


class LRUCache {
public:
    //构造:提供最大容量
    LRUCache(int capacity) {
        this->capacity = capacity;
    }

    //获取某键的缓存值
    int get(int key) {
        //没有该键的缓存值
        if (Map.find(key) == Map.end()) {
            return -1;
        }
        //将该键提升为最近使用的键
        makeRencent(key);
        return Map[key]->val;
    }

    //存放某键的缓存值
    void put(int key, int val) {
        //缓存键已存在,则更新键值
        if (Map.find(key) != Map.end()) {
            deleteKey(key);
            addRencent(key, val);
            return;
        }
        //如果已满,则淘汰最近最少使用的缓存
        if (cache.getSize() == capacity) {
            removeLeastRecently();
        }
        //添加为最近使用的元素
        addRencent(key, val);
    }

    //测试用,按LRU顺序展示缓存中所有内容
    void show(){
        cache.show();
        cout<<endl;
    }

    //将某个key提升为最近使用的
    void makeRencent(int key) {
        Node *x = Map[key];
        cache.remove(x);
        cache.addLast(x);
    }

    //添加最近使用的元素
    void addRencent(int key, int val) {
        Node *x = new Node(key, val);
        Map[key] = x;
        cache.addLast(x);
    }

    //删除某个key
    void deleteKey(int key) {
        Node *x = Map[key];
        cache.remove(x);
        Map.erase(key);
        free(x);
    }

    /*删除最久未使用元素*/
    void removeLeastRecently() {
        Node *target = cache.removeFirst();
        int key = target->val;    //这就是为什么双向链表中节点要存储key,是为了删除的时候,反向查找在map中的位置并删除
        Map.erase(key);
        free(target);
    }

private:
    //哈希表
    map<int, Node *> Map;
    DoubleList cache;
    int capacity;
};


int main() {
    LRUCache lru(4);
    while(1){
        string op;
        cin>>op;
        if(op=="get"){
            int key;
            cin>>key;
            int res=lru.get(key);
            if(res==-1){
                cout<<"Not exist!"<<endl;
                continue;
            }
            cout<<key<<":"<<res<<endl;
            lru.show();
        }else if(op=="put"){
            int key,val;
            cin>>key>>val;
            lru.put(key,val);
            lru.show();
        }else{
            cout<<"wrong command!"<<endl;
            continue;
        }
    }
}

2021-7-18

  • 并查集Union-find算法,主要解决的是图论中的动态连通性
  • 并查集数组实现(路径压缩)
#include <iostream>
using namespace std;
#define MAXN 10

int fa[MAXN+1];

void init(){
    for(int i=1;i<=MAXN;i++)
        fa[i]=i;
}

//返回当前节点的根节点,并进行路径压缩
int find(int x){
    if(fa[x]==x)
        return x;
    fa[x]=find(fa[x]);
    return fa[x];
}

//合并两个节点,注意在同一棵树上的问题
void merge(int a,int b){
    fa[find(a)]=find(b);
}
  • 按秩合并优化版:
    路径压缩版本只能在find的时候实现,因此合并过程中,还是会出现很复杂的树。为了避免树过深,可以选择按秩合并(即高度低的树,合并到高度高的树上,来避免树高度的增长,当然两棵树高度相同的情况下,合并后必然有一棵树高度会+1)
#include <iostream>
using namespace std;
const int MAXN = 7;
int fa[MAXN+1];
int rank_[MAXN + 1];

void init(){
    for(int i=1;i<=MAXN;i++){
        fa[i]=i;
        rank_[i]=1;
    }
}

int find(int x){
    if(x==fa[x]){
        rank_[x]=1;
        return x;
    }
    fa[x]=find(fa[x]);
    rank_[x]=2;
    return fa[x];
}

void merge(int a,int b){
    int ap=find(a);
    int bp=find(b);
    //秩(高度)低的,合并到秩高的树上
    if(rank_[ap] > rank_[bp]){
        fa[bp]=ap;
    }else{
        fa[ap]=bp;
    }
    //如果两棵树高度相同,那么合并后,合并的目标树高度要加一(排除同一棵树的情形)
    if(rank_[ap] == rank_[bp] && ap != bp){
        rank_[bp]++;
    }
}

2021-7-17

  • 今天做一道leetcode题目1373二叉搜索子树的最大键值和
    • 我花了两个小时的时间,研究怎么在一个非BST的子树中,找出最大BST键值和,费了老劲。最后发现题解中,只需要用一个flagMAX记录一下最大值即可啊!!!气。
    • 还吸取了一个教训,找BST的时候,空节点直接视为一棵空BST,有时更为简单。
  • 关于判断BST合法性这个问题,有三种思路
    1. 利用BST中序遍历有序这个性质,先对该树进行中序遍历,再判断是否是升序即可。
    2. 对于树的操作应该着眼于当前节点,根据BST的定义,当前节点的值应该大于所有左子树节点的值(大于左子树最大值),且小于所有右子树节点的值( 小于右子树最小值)。据此可以写出递归函数,不过要在空节点的返回值上要加以设计。
    3. 本题还可以为为每个节点设置阈值(上限和下限),通过递归传递和更新阈值。(此方法把节点之间缠绕在一起,单独做此题目容易,如果BST和其他问题结合,则不好处理)
class Solution {
public:
    bool isValidBST(TreeNode* root) {
        return checkBST(root,LONG_LONG_MIN,LONG_LONG_MAX);
    }
    bool checkBST(TreeNode*root,long long low,long long high){
    if(!root)
        return true;
    if(root->val<=low || root->val>=high)
        return false;
    return checkBST(root->left,low,root->val)
    && checkBST(root->right,root->val,high);
    }
};
class Solution {
public:
//树的递归中,以当前节点为思考对象,最佳
    bool isValidBST(TreeNode* root) {
        return checkBST(root)[0];
    }
    //三个返回值:1.是否是BST 2.最小值 3. 最大值
    vector<long long>checkBST(TreeNode*root){
        if(!root)
            return {1,LONG_LONG_MAX,LONG_LONG_MIN};
        vector<long long>l_res=checkBST(root->left);
        vector<long long>r_res=checkBST(root->right);
        if(l_res[0] && r_res[0] && root->val>l_res[2] && root->val<r_res[1]){
            long long l_min=l_res[1];
            long long r_max=r_res[2];
            return {1,l_min!=LONG_LONG_MAX?l_min:root->val,r_max!=LONG_LONG_MIN?r_max:root->val};    
        }
        return {0,0,0};
    }
};

2021-7-16

  • 昨天会写的二叉树的序列化,今天再去想这个问题,竟然不会写。发现自己还是没有完全领悟二叉树和递归的思维。二叉树中递归的思维,应该着眼于根节点,处理好一个节点的行为,递归下去便完成了对整棵树的操作。
    • 考虑二叉树序列化这个问题,对于以root为根节点的二叉树来说,先得直到左子树和右子树的序列化内容,才能返回左子树序列化+根节点val字符串+右子树序列化,因此要使用后序遍历
    • 需要先直到子树内容,才能知道当前树内容的,通通用后续遍历。
//用**后序遍历**的方式,实现二叉树**前序的序列化**
 string serializeTree(TreeNode*root){
     //终止条件
     if(!root)
         return "";
     //逻辑
     string ans=intToStr(root->val);
     string lStr=serializeTree(root->left);
     string rStr=serializeTree(root->right);
     if(lStr!=""){
         ans+=','+lStr;
     }
     if(rStr!=""){
         ans+=','+rStr;
     }
     return ans;
 }
 string intToStr(int n){
     string s;
     stringstream ss;
     ss<<n;
     ss>>s;
     ss.clear();
     return s;
    }
  • 今天对补码有了全新的认识
    其中1000 0000 是规定的最小值,然后简单+1就是-127,可以一直简单+1到127
    补码的好处就是让如1-2的运算,变成硬件最简单操作的相加运算
    1-2=0000 0001+1111 1110=1111 1111=-1
    在这里插入图片描述

  • C++中如何获取int型的最大值和最小值

    • 方法一:头文件climits中有INT_MAX,INT_MIN
    • 方法二:
      • 最小值:1<<31,即1000 0000...0000,补码规定的负数的最小值, − 2 31 − 1 -2^{31}-1 2311
      • 最大值:unsigned int a=0; ~a>>1即为0111 1111...1111 2 31 2^{31} 231

2021-7-15

  • 二叉搜索树BST
    • 判断一棵树是否是BST:需要借助两个变量记录每个节点应该遵循的上限和下限,然后递归即可
    • 增加节点:递归找到位置(NULL),然后接在上面即可。
    • 删除节点:

      第五种情况,操作如下:
      第一步

      第二步

      第三步

      第四步

2021-7-14

  • 二叉搜索树(BST,Binary Search Tree)是递归定义的
    • BST为空,或者
    • 对于二叉搜索树的每一个节点root,左子树所有节点得值小于root值,右子树所有节点的值大于root值
    • 对于BST的每一个节点,它的左子树和右子树都是BST
    • 没有键值相等的结点
  • 二叉搜索树的重要性质:中序遍历是有序的
  • 二叉树的各种递归,无非是:1.前序遍历 2.中序遍历 3.后序遍历
    • 二叉树的序列化,使用后序遍历(注意要以#处理叶节点的子节点)
  • 对于二叉树的递归构造,应该想方设法构造根节点,只要根节点构造出来了,其他所有节点根据递归就全部都构造出来了。

2021-7-13

  • 快慢指针、双指针、多指针,在链式结构中是十分重要的思想。
  • ”链表是一种兼具迭代性质和递归性质的数据结构”,很有道理。
  • 今天做反转链表这道题目,迭代和递归方法都非常精妙,其中博主labuladong对递归的思考方式令我印象深刻,受益匪浅。
    Leetcode206 反转链表
    • “不要跳进递归(你的脑袋能压几个栈呀?)”,博主如是说



      下面这一步十分精妙

    • 我认为,实现递归的代码,不应该跳进递归的轮回中,应该假装递归方法已经实现,直接调用,然后补其具体递归逻辑。
    • 此外,递归最为重要的就是,递归函数的含义

2021/7/11

  • 对于递归函数来说,最重要的就是递归函数的含义
    • 写递归算法的关键是要明确函数的「定义」是什么,然后相信这个定义,利用这个定义推导最终结果,绝不要跳入递归的细节。

2021/6/3

  • 做dp的题目,首先要搞清楚状态转移方程,然后应该举个小例子草稿实现一下dp,以使对边界条件有个清晰的认识。
  • 关于字符串的dp,经常把dp[i]定义成以i位置字符结尾的串的个数

2021/6/2

  • 遇到问题类似最少多步、最少多少次这类问题,一定要想到用bfs或者最短路径算法
  • 蓝桥杯国赛比赛之前默念:搜索、位运算、全排列、数学问题
  • 当设计递归函数遇到困难,搞不清楚时,举个例子模拟一下过程就好了。
  • 当算法中出现大量重复问题的时候,可以采用记录的方法,可以大大降低时间复杂度

2021/6/1

  • 感悟:今天重做蓝桥杯题目对局匹配的时候,更加深刻地明白了两个道理
    • 懂和会写,是完全两件事,明白怎么写不等于能写出通过oj的代码
    • 编程不能闭门造车,要吸收别人优秀的代码和思想。
  • 遇到统计方案数的题目,一定要注意重复问题
  • dfs有爆栈的风险,可以用bfs来替代
  • 对于图的相关问题如发现环,或者排序,首先要想到拓扑排序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值