LeetCode 1938解题报告

目录

1. 题意

2. 方法

 离线算法 + 01 Trie


1. 题意

        给定一棵有n个节点的有根树,m组询问[idx, val],询问从根节点到node[idx]的路径中与val异或的最大值

        LeetCode 1938

2. 方法

  1.  离线算法 + 01 Trie

    1. 思路与算法

      1. 01Trie

        1. 维护一棵Trie树,查询到当前节点时,Trie树上存放的便是从根节点到当前节点的所有元素的二进制表示,用left边表示0,用right边表示1,使用cnt标记此处的0或1是有多少个节点经过的(方便进行删除与查询),结构体定义如下

          struct Trie {
              Trie* left;
              Trie* right;
              // cnt表示当前节点出现的次数,在插入和删除节点的时候进行维护,在查询的时候要根据cnt==0判断当前节点是否存在
              int cnt;
              Trie() : left(nullptr), right(nullptr), cnt(0) {} 
          };

          需要注意的是本题不需要记录Trie树的结束标志,根据题意,最大val值为2^{17}<2*10^{5}<2^{18},  也就是说最大的二进制位数是18(0-17),由此设计成对于每个数来说都是等高的树高即可

        2. trie insert

          1. 依次找u的各个二进制位,如果是1,那么查看right指针是否为空,如果为空,那么新建right指针;否则,相同的步骤对应到left指针,接着把cur指针移动到left指针或者right指针上即可,需要维护cnt,表示当前Trie节点是有几个数共同占有的

        3. trie query

          1. 因为要做异或运算,根据常识,相异为1,相同为0,我们只关心为1的二进制位,要使得异或值最大-->1的二进制位尽可能多-->如果val当前二进制位是1,优先找left指针,否则,优先找right指针,如果当前结果二进制位可以为1,那么ret |= (1 << i)

        4. trie erase

          1. 删除的时候只需要把u所经过的节点的cnt值--即可,这意味着当前节点在字典树中被移除

      2. 离线算法

        1. 对于询问数组,我们把每个  node[idx] 对应的在询问数组中的下标和要询问的数val记录下来,当到达node节点时,当前字典树中存放的便是从根节点到node节点的元素,依次处理val后存入结果数组对应位置即可

      3. ​​​​​​​dfs回溯

        1. ​​​​​​​首先需要建图

        2. 遍历的时候,从root开始,首先执行trie_insert(u), 把当前u加入字典树,通过离线字典,我们处理与当前节点u有关的询问,询问结束后,向下遍历u的子节点,当递归返回时,意味着u及u的子节点都已询问完成,此时在trie树中消除u的影响,并向上返回

    2. 复杂度分析

      1. ​​​​​​​时间复杂度:O((n+q)logc),n是节点的数目,q为询问的个数,对于每次添加、删除和查询操作,我们都需要logc次操作(18次,树高)

      2. 空间复杂度:O(n+nlogc+2*q),依次是存图,存Trie,存询问和结果数组所需要的空间

    3. ​​​​​​​​​​​​​​​​​​​​​代码实现

struct Trie {
    Trie* left;
    Trie* right;
    // cnt表示当前节点出现的次数,在插入和删除节点的时候进行维护,在查询的时候要根据cnt==0判断当前节点是否存在
    int cnt;
    Trie() : left(nullptr), right(nullptr), cnt(0) {} 
};
class Solution {
private:
    static constexpr int MAXD = 17;
public:
    // 01 Trie + 离线
    vector<int> maxGeneticDifference(vector<int>& parents, vector<vector<int>>& queries) {
        // 把parent数组存为图
        int n = parents.size();
        vector<vector<int>> edges(n);
        // edges.clear();
        int root = -1;
        for (int i = 0; i < n; i++) {
            if (parents[i] == -1) {
                root = i;
            } else {
                edges[parents[i]].emplace_back(i);
            }
        }
        // dfs回溯建立字典树
        // 离线
        vector<vector<pair<int, int>>> stored(n);
        stored.clear();
        int q = queries.size();
        vector<int> ans(q);
        for (int i = 0; i < q; i++) {
            stored[queries[i][0]].emplace_back(i, queries[i][1]);
        }
        Trie* r = new Trie();
        // 向字典树中新增一个数
        auto trie_insert = [&](int u) {
            Trie* cur = r;
            for (int i = MAXD; i >= 0; i--) {
                if (u & (1 << i)) {
                    // 向右表示一个1
                    // 这里直接new会导致时间变得很大,正确做法是放到if语句中在需要的时候开辟空间
                    // Trie* right = new Trie();
                    if (!(cur->right)) {
                        cur->right = new Trie();
                    }
                    cur = cur->right;
                    cur->cnt++;
                } else {
                    // 向左表示一个0
                    // 这里同理,在if外面new会使得时间消耗比较大
                    // Trie* left = new Trie();
                    if (!(cur->left)) {
                        cur->left = new Trie();
                    }
                    cur = cur->left;
                    cur->cnt++;
                }
            }
        };
        // 查询x在字典树中最大的异或值
        auto trie_query = [&](int x) -> int {
            Trie* cur = r;
            int ret = 0;
            for (int i = MAXD; i >= 0; i--) {
                if (x & (1 << i)) {
                    if (cur->left && cur->left->cnt) {
                        ret |= (1 << i);
                        cur = cur->left;
                    } else {
                        cur = cur->right;
                    }
                } else {
                    if (cur->right && cur->right->cnt) {
                        ret |= (1 << i);
                        cur = cur->right;
                    } else {
                        cur = cur->left;
                    }
                }
            }
            return ret;
        };
        // 在字典树中删除一个数
        auto trie_erase = [&](int u) {
            Trie* cur = r;
            for (int i = MAXD; i >= 0; i--) {
                if (u & (1 << i)) {
                    cur = cur->right;
                } else {
                    cur = cur->left;
                }
                cur->cnt--;
            }
        };
        // dfs回溯, 到达当前节点时查看是否进行查询,当从当前节点结束递归时消除节点值带来的影响,保证结果正确性
        function<void(int)> dfs = [&](int root) {
            trie_insert(root);
            // 注意这种写法,用来遍历pair数组
            for (auto [idx, num] : stored[root]) {
                ans[idx] = trie_query(num);
            }
            for (int v : edges[root]) {
                dfs(v);
            }
            trie_erase(root);
        };
        dfs(root);
        return ans;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值