六月集训(13,15) 双向链表,深度优先搜索

双向链表

1.LeetCode:432. 全 O(1) 的数据结构

原题链接


        请你设计一个用于存储字符串计数的数据结构,并能够返回计数最小和最大的字符串。

        实现 AllOne 类:

        AllOne() 初始化数据结构的对象。

        inc(String key) 字符串
        key 的计数增加 1 。如果数据结构中尚不存在 key ,那么插入计数为 1 的 key 。

        dec(String key) 字符串 key 的计数减少 1 。如果 key 的计数在减少后为 0 ,那么需要将这个 key 从数据结构中删除。测试用例保证:在减少计数前,key 存在于数据结构中。

        getMaxKey() 返回任意一个计数最大的字符串。如果没有元素存在,返回一个空字符串 “” 。

        getMinKey() 返回任意一个计数最小的字符串。如果没有元素存在,返回一个空字符串 “” 。

        注意:每个函数都应当满足 O(1) 平均时间复杂度。

        示例:

        输入

        [“AllOne”, “inc”, “inc”, “getMaxKey”, “getMinKey”, “inc”, “getMaxKey”, “getMinKey”]

        [[], [“hello”], [“hello”], [], [], [“leet”], [], []]

        输出

        ;[null, null, null, “hello”, “hello”, null, “hello”, “leet”]

        提示:

        1 <= key.length <= 10

        key 由小写英文字母组成

        测试用例保证:在每次调用 dec 时,数据结构中总存在 key

        最多调用 inc、dec、getMaxKey 和 getMinKey 方法 5 * 1e4 次


        先看接口,要我们在O(1)的时间内进行插入,删除,返回频率最大最小这四个操作。插入,删除这些操作在O(1)时间复杂度内完成的可以是链表,双向链表,哈希表,同时又要返回出现频率最大和最小的key值,所以这里就选择利用双向链表和哈希表来配合使用了。

        这里我们进行一些准备工作,比如结构体的定义,和我们自己需要用到的接口的实现。对于结构体而言,他是我们的双向链表,除了必须的prev和next之外值域为key和key出现的频率cnt。并且初始化时都置空。

	struct Node{
        string key;
        int cnt;
        Node* prev,* next;
        Node(){
            prev=next=nullptr;
            cnt=0;
        }
    };


        同时由于我们又想要得到出现频率最大和最小的数据,这里就不妨把双向链表的头定义为出现频率最小的元素,把尾定义为出现频率最大的元素。

        这里的插入操作很简单,我们这里选择对于新元素进行头插,也即是如下操作:

	void insert(Node* node){
        if(!head){
            head=tail=node;
            return ;
        }else {
            head->prev=node;
            node->next=head;
            head=node;
        }
    }


        而删除操作也很简单,对于prev和next分别进行操作,先判空然后交换即可。

	void del(Node* node){
        if(node->prev){
            node->prev->next=node->next;
        }else {
            head=node->next;
        }
        if(node->next){
            node->next->prev=node->prev;
        }else {
            tail=node->prev;
        }
    }


        而对于将频率控制我们这里进行排序,插入的时候我们从头开始向后降序升序排序,删除的时候我们从他向前开始进行升序排序。这里进行的排序即可,因为我们一直都在排序,所以每次进行排序的时候数组元素有序性比较强,消耗也少。只不过在排序的过程中,也要对哈希表进行交换,因为如果不交换哈希表的val的话,他们的key对应着的都是原先对方的value。

	 void sort_front(Node* node){
        while(node&&node->next){
            if(node->cnt>node->next->cnt){
                Node* nnext=node->next;
                swap(node->key,nnext->key);
                swap(node->cnt,nnext->cnt);
                Node* tmp=map[node->key];
                map[node->key]=map[nnext->key];
                map[nnext->key]=tmp;
            }
            node=node->next;
        }
    }//从头开始向后排序
    void sort_back(Node* node){
        while(node&&node->prev){
            if(node->cnt<node->prev->cnt){
                Node* nprev=node->prev;
                swap(node->key,nprev->key);
                swap(node->cnt,nprev->cnt);
                Node* tmp=map[node->key];
                map[node->key]=map[nprev->key];
                map[nprev->key]=tmp;
            }
            node=node->prev;
        }
    }//从node开始向前排序


        接口处理完毕,剩下的就是实现题目的接口了,对于插入而言如果原先没有出现过就new出来然后头插,否则找到他频率加一。对于删除而言,题目中说过了他之前一定出现过,所以这里直接减少频率即可,当次数为0进行删除。得到最小和最大频率的数据直接返回头和尾的key即可。
        完整代码如下:

class AllOne {
    struct Node{
        string key;
        int cnt;
        Node* prev,* next;
        Node(){
            prev=next=nullptr;
            cnt=0;
        }
    };
    Node* head,* tail;
    unordered_map<string,Node*> map;
    void swap(string &a,string& b){
        string tmp=a;
        a=b;
        b=tmp;
    }
    void swap(int& a,int& b){
        int tmp=a;
        a=b;
        b=tmp;
    }
public:
    void insert(Node* node){
        if(!head){
            head=tail=node;
            return ;
        }else {
            head->prev=node;
            node->next=head;
            head=node;
        }
    }
    void del(Node* node){
        if(node->prev){
            node->prev->next=node->next;
        }else {
            head=node->next;
        }
        if(node->next){
            node->next->prev=node->prev;
        }else {
            tail=node->prev;
        }
    }
    void sort_front(Node* node){
        while(node&&node->next){
            if(node->cnt>node->next->cnt){
                Node* nnext=node->next;
                swap(node->key,nnext->key);
                swap(node->cnt,nnext->cnt);
                Node* tmp=map[node->key];
                map[node->key]=map[nnext->key];
                map[nnext->key]=tmp;
            }
            node=node->next;
        }
    }
    void sort_back(Node* node){
        while(node&&node->prev){
            if(node->cnt<node->prev->cnt){
                Node* nprev=node->prev;
                swap(node->key,nprev->key);
                swap(node->cnt,nprev->cnt);
                Node* tmp=map[node->key];
                map[node->key]=map[nprev->key];
                map[nprev->key]=tmp;
            }
            node=node->prev;
        }
    }
    AllOne() {
        head=tail=nullptr;
        map.clear();
    }
    
    void inc(string key) {
        Node* tmp;
        auto it=map.find(key);
        if(it==map.end()){
            tmp=new Node();
            tmp->key=key;
            tmp->cnt++;
            map[key]=tmp;
            insert(tmp);
        }else {
            tmp=it->second;
            tmp->cnt++;
        }
        sort_front(tmp);
    }
    
    void dec(string key) {
        auto it=map.find(key);
        Node* tmp;
        tmp=it->second;
        tmp->cnt--;
        if(!tmp->cnt){
            del(tmp);
        }else {
            sort_back(tmp);
        }
    }
    
    string getMaxKey() {
        return tail==nullptr?"":tail->key;
    }
    
    string getMinKey() {
        return head==nullptr?"":head->key;
    }
};

深度优先搜索:

1.LeetCode:508. 出现次数最多的子树元素和

原题链接


        给你一个二叉树的根结点 root ,请返回出现次数最多的子树元素和。如果有多个元素出现的次数相同,返回所有出现次数最多的子树元素和(不限顺序)。

        一个结点的 「子树元素和」 定义为以该结点为根的二叉树上所有结点的元素之和(包括结点本身)。

        示例 1:

        输入: root = [5,2,-3]

        输出: [2,-3,4]

        示例 2:

        输入: root = [5,2,-5]

        输出: [2]

        提示:

        节点数在 [1, 1e4] 范围内

        -1e5 <= Node.val <= 1e5


        没什么好说的,树形Dp加哈希表计数即可。

class Solution {
    unordered_map<int,int> map;
    int max;
    int get_cnt(TreeNode* root){
        if(root==nullptr){
            return 0;
        }
        int sum=root->val;
        sum+=get_cnt(root->left);
        sum+=get_cnt(root->right);
        map[sum]++;
        max=max<map[sum]?map[sum]:max;
        return sum;
    }
public:
    vector<int> findFrequentTreeSum(TreeNode* root) {
        if(!root){
            return {};
        }
        map.clear();
        max=0;
        get_cnt(root);
        vector<int> ans;
        for(auto i:map){
            if(i.second==max){
                ans.push_back(i.first);
            }
        }
        return ans;
    }
};

2.LeetCode:1026. 节点与其祖先之间的最大差值

原题链接


        给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。(如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先)

        示例 1:

        输入:root = [8,3,10,1,6,null,14,null,null,4,7,13]

        输出:7

        示例 2:

        输入:root = [1,null,2,null,0,3]

        输出:3

        提示:

        树中的节点数在 2 到 5000 之间。

        0 <= Node.val <= 1e5


        一种比较容易理解的方法就是,同样利用树形Dp进行前序遍历,不断维护到每个结点的最大和最小父结点的值然后维护答案。因为最大距离可以是父减去最小的子或最大的子减去父,也可以是最大的父减去子或者子减去最小的父。(这里的说法是有问题的,理解意思即可)

class Solution {
    int ans;
    void  dfs(TreeNode* root,int max,int min){
        if(!root){
            return;
        }
        int number=abs(root->val-max)>abs(root->val-min)?abs(root->val-max):abs(root->val-min);
        ans=ans>number?ans:number;
        min=min<root->val?min:root->val;
        max=max>root->val?max:root->val;
        dfs(root->left,max,min);
        dfs(root->right,max,min);
    }
public:
    int maxAncestorDiff(TreeNode* root) {
        if(!root){
            return 0;
        }
        ans=0;
        dfs(root,root->val,root->val);
        return ans;
    }
};

3.LeetCode:1600. 王位继承顺序

原题链接


        一个王国里住着国王、他的孩子们、他的孙子们等等。每一个时间点,这个家庭里有人出生也有人死亡。

        这个王国有一个明确规定的王位继承顺序,第一继承人总是国王自己。我们定义递归函数 Successor(x, curOrder) ,给定一个人 x 和当前的继承顺序,该函数返回 x 的下一继承人。

        Successor(x, curOrder):

         如果 x 没有孩子或者所有 x 的孩子都在 curOrder 中:

        如果 x 是国王,那么返回 null

        否则,返回 Successor(x 的父亲, curOrder)

        否则,返回 x 不在 curOrder 中最年长的孩子

        比方说,假设王国由国王,他的孩子 Alice 和 Bob (Alice 比 Bob 年长)和 Alice 的孩子 Jack 组成。

        一开始, curOrder 为 [“king”].

        调用 Successor(king, curOrder) ,返回 Alice ,所以我们将 Alice 放入 curOrder 中,得到 [“king”, “Alice”] 。

        调用 Successor(Alice, curOrder) ,返回 Jack ,所以我们将 Jack 放入 curOrder 中,得到 [“king”, “Alice”, “Jack”] 。

        调用 Successor(Jack, curOrder) ,返回 Bob ,所以我们将 Bob 放入 curOrder 中,得到 [“king”, “Alice”, “Jack”, “Bob”] 。

        调用 Successor(Bob, curOrder) ,返回 null 。最终得到继承顺序为 [“king”, “Alice”, “Jack”, “Bob”] 。

        通过以上的函数,我们总是能得到一个唯一的继承顺序。

        请你实现 ThroneInheritance 类:

        ThroneInheritance(string kingName) 初始化一个 ThroneInheritance 类的对象。国王的名字作为构造函数的参数传入。

        void birth(string parentName, string childName) 表示 parentName 新拥有了一个名为 childName 的孩子。

        void death(string name) 表示名为 name 的人死亡。
        一个人的死亡不会影响 Successor 函数,也不会影响当前的继承顺序。你可以只将这个人标记为死亡状态。

        string[] getInheritanceOrder() 返回 除去 死亡人员的当前继承顺序列表。

        示例:

        输入:

        [“ThroneInheritance”, “birth”, “birth”, “birth”, “birth”, “birth”, “birth”, “getInheritanceOrder”, “death”, “getInheritanceOrder”]

        [[“king”], [“king”, “andy”], [“king”, “bob”], [“king”, “catherine”], [“andy”, “matthew”], [“bob”, “alex”], [“bob”, “asha”], [null], [“bob”], [null]]

        输出:

        [null, null, null, null, null, null, null, [“king”, “andy”, “matthew”, “bob”, “alex”, “asha”, “catherine”], null, [“king”, “andy”, “matthew”, “alex”, “asha”, “catherine”]]

        提示:

        1 <= kingName.length, parentName.length, childName.length, name.length <= 15

        kingName,parentName, childName 和 name 仅包含小写英文字母。

        所有的参数 childName 和 kingName 互不相同。

        所有 death 函数中的死亡名字 name 要么是国王,要么是已经出生了的人员名字。

        每次调用 birth(parentName, childName) 时,测试用例都保证 parentName 对应的人员
        最多调用 1e5 次birth 和 death 。

        最多调用 10 次 getInheritanceOrder 。


        这种类型的题目还是很简单的,这道题就是很典型的DFS问题:搜索多叉树。我们先自定义一个结构体Tree他拥有的成员变量有name和childeren以及是否死亡的标志die。初始化都置空。
        此外我们还需要一个哈希表,key为stiring,value为Tree*,因为题目给我们的数据都是名字,需要把名字和数据对应起来。

	struct Tree{
        string name;
        bool die;
        vector<Tree*> children;
        Tree(){
            name="";
            die=false;
            children.clear();
        }
    };
    Tree* add(string s){
        Tree* cur=new Tree();
        cur->name=s;
        map[s]=cur;
        return cur;
    }//插入操作


        然后就是各个接口的实现了。

        ThroneInheritance没什么好说的直接插入即可,并依次为头。

        birth操作就把对应的父母的children插入childname即可。

        death将对应的元素的die置为true。

        getInheritanceOrder就是简单的多叉树搜索,因为我们对于children的插入是按照出手顺序来的,所以直接进行dfs,先查看国王的状态,然后dfs他的每一个孩子,按照年龄顺序和是否存活来加入到答案数组中。

        完整代码如下:

class ThroneInheritance {
    struct Tree{
        string name;
        bool die;
        vector<Tree*> children;
        Tree(){
            name="";
            die=false;
            children.clear();
        }
    };
public:
    unordered_map<string,Tree*> map;
    Tree* root;
    Tree* add(string s){
        Tree* cur=new Tree();
        cur->name=s;
        map[s]=cur;
        return cur;
    }
    ThroneInheritance(string kingName) {
        root=add(kingName);
    }
    
    void birth(string parentName, string childName) {
        map[parentName]->children.push_back(add(childName));
    }
    
    void death(string name) {
        map[name]->die=true;
    }
    void get_order(vector<string>& ans,Tree* root){
        if(root){
            if(!root->die){
                ans.push_back(root->name);
            }
            for(auto i:root->children){
                get_order(ans,i);
            }
        }
    }
    vector<string> getInheritanceOrder() {
        vector<string> ans;
        get_order(ans,root);
        return ans;
    }
};

4.LeetCode:655. 输出二叉树

原题链接


       在一个 m*n 的二维字符串数组中输出二叉树,并遵守以下规则:

       行数 m 应当等于给定二叉树的高度。

       列数 n 应当总是奇数。

       根节点的值(以字符串格式给出)应当放在可放置的第一行正中间。根节点所在的行与列会将剩余空间划分为两部分(左下部分和右下部分)。你应该将左子树输出在左下部分,右子树输出在右下部分。左下和右下部分应当有相同的大小。即使一个子树为空而另一个非空,你不需要为空的子树输出任何东西,但仍需要为另一个子树留出足够的空间。然而,如果两个子树都为空则不需要为它们留出任何空间。

       每个未使用的空间应包含一个空的字符串"“。

       使用相同的规则输出子树。
输入:
1
/
2 5
/
3
/
4
输出:
[[”“, “”, “”, “”, “”, “”, “”, “1”, “”, “”, “”, “”, “”, “”, “”]
[”“, “”, “”, “2”, “”, “”, “”, “”, “”, “”, “”, “5”, “”, “”, “”]
[”", “3”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”]
[“4”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”, “”]]
注意: 二叉树的高度在范围 [1, 10] 中。


       这道题就很明显是树形DP和分治的结合了,先通过树形dp获得二叉树的深度进而得到他的个数然后进行分治即可。分治的过程也很简单,把数据放到当前范围的中间位置即可,然后从中间位置继续左右分治。

class Solution {
    int get_dep(TreeNode* root){
        if(!root){
            return 0;
        }
        return max(get_dep(root->left),get_dep(root->right))+1;
    }
    string int_to_string(int num){
        if(num==0){
            return "0";
        }
        bool flag=false;
        string ans="";
        if(num<0){
            flag=true;
            num*=-1;
        }
        while(num){
            ans.push_back(num%10+'0');
            num/=10;
        }
        if(flag){
            ans+="-";
        }
        reverse(ans.begin(),ans.end());
        return ans;
    }
    void dfs(TreeNode* root,int l,int r,int curdep,vector<vector<string>>& ans){
        if(!root){
            return ;
        }
        int mid=l+((r-l)>>1);
        ans[curdep][mid]=int_to_string(root->val);
        dfs(root->left,l,mid-1,curdep+1,ans);
        dfs(root->right,mid+1,r,curdep+1,ans);
    }
public:
    vector<vector<string>> printTree(TreeNode* root) {
        int dep=get_dep(root);
        int cnt=(1<<dep)-1;
        vector<vector<string>> ans(dep,vector<string>(cnt,""));
        dfs(root,0,cnt-1,0,ans);
        return ans;

    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值