力扣之Trie(字典树、前缀树)系列

力扣之Trie(字典树、前缀树)系列

Leetcode 0208 实现Trie(前缀树)

题目描述:Leetcode 0208 实现Trie(前缀树)

在这里插入图片描述

分析

  • 本题的考点:trie

  • trie又被称为前缀树、字典树。

  • 关于trie可以参考:网址

代码

  • C++
class Trie {
public:
    struct Node {
        bool is_end;
        Node *son[26];

        Node() {
            is_end = false;
            for (int i = 0; i < 26; i++)
                son[i] = NULL;
        }
    } *root;

    /** Initialize your data structure here. */
    Trie() {
        root = new Node();
    }

    /** Inserts a word into the trie. */
    void insert(string word) {
        auto p = root;
        for (auto c : word) {
            int u = c - 'a';
            if (!p->son[u]) p->son[u] = new Node();
            p = p->son[u];
        }
        p->is_end = true;
    }

    /** Returns if the word is in the trie. */
    bool search(string word) {
        auto p = root;
        for (auto c : word) {
            int u = c - 'a';
            if (!p->son[u]) return false;
            p = p->son[u];
        }
        return p->is_end;
    }

    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string word) {
        auto p = root;
        for (auto c : word) {
            int u = c - 'a';
            if (!p->son[u]) return false;
            p = p->son[u];
        }
        return true;
    }
};
  • Java
public class Trie {
    private class Node{

        public boolean isWord;
        public Node[] next;

        public Node(boolean isWord){
            this.isWord = isWord;
            next = new Node[26];
        }

        public Node(){
            this(false);
        }
    }

    private Node root;

    public Trie(){
        root = new Node();
    }

    // 向Trie中添加一个新的单词word
    public void insert(String word){

        Node cur = root;
        for(int i = 0 ; i < word.length() ; i ++){
            char c = word.charAt(i);
            if(cur.next[c-'a'] == null)
                cur.next[c-'a'] = new Node();
            cur = cur.next[c-'a'];
        }
        cur.isWord = true;
    }

    // 查询单词word是否在Trie中
    public boolean search(String word){

        Node cur = root;
        for(int i = 0 ; i < word.length() ; i ++){
            char c = word.charAt(i);
            if(cur.next[c-'a'] == null)
                return false;
            cur = cur.next[c-'a'];
        }
        return cur.isWord;
    }

    // 查询是否在Trie中有单词以prefix为前缀
    public boolean startsWith(String isPrefix){

        Node cur = root;
        for(int i = 0 ; i < isPrefix.length() ; i ++){
            char c = isPrefix.charAt(i);
            if(cur.next[c-'a'] == null)
                return false;
            cur = cur.next[c-'a'];
        }

        return true;
    }
}

时空复杂度分析

  • 时间复杂度:每一个操作都是 O ( l e n ) O(len) O(len)len为单词平均长度。

  • 空间复杂度: O ( n × l e n ) O(n \times len) O(n×len)n为插入的单词数量。

Leetcode 0211 添加与搜索单词

题目描述:Leetcode 0211 添加与搜索单词

在这里插入图片描述

分析

  • 本题的考点:trie

  • trie插入操作不变,查询的时候遇到字母正常处理,遇到通配符直接暴搜。

代码

  • C++
class WordDictionary {
public:
    struct Node {
        bool is_end;
        Node *son[26];

        Node() {
            is_end = false;
            for (int i = 0; i < 26; i++)
                son[i] = NULL;
        }
    } *root;

    /** Initialize your data structure here. */
    WordDictionary() {
        root = new Node();
    }

    void addWord(string word) {
        auto p = root;
        for (auto c : word) {
            int u = c - 'a';
            if (!p->son[u]) p->son[u] = new Node();
            p = p->son[u];
        }
        p->is_end = true;
    }

    bool search(string word) {
        return dfs(root, word, 0);
    }

    // 返回以p为根的trie树中是否存在字符串word[i...)
    bool dfs(Node *p, string word, int i) {
        if (i == word.size()) return p->is_end;
        if (word[i] != '.') {
            int u = word[i] - 'a';
            if (!p->son[u]) return false;
            return dfs(p->son[u], word, i + 1);
        } else {
            for (int j = 0; j < 26; j++)
                if (p->son[j] && dfs(p->son[j], word, i + 1))
                    return true;
            return false;
        }
    }
};
  • Java
public class WordDictionary {

    private class Node {

        public boolean isWord;
        public TreeMap<Character, Node> next;

        public Node(boolean isWord) {
            this.isWord = isWord;
            next = new TreeMap<>();
        }

        public Node() {
            this(false);
        }
    }

    private Node root;

    public WordDictionary() {
        root = new Node();
    }

    public void addWord(String word) {
        Node cur = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (cur.next.get(c) == null)
                cur.next.put(c, new Node());
            cur = cur.next.get(c);
        }
        cur.isWord = true;
    }

    public boolean search(String word) {
        return match(root, word, 0);
    }

    // 在以node为根节点的字典树中查找是否存在单词word,index表示匹配到第几个字符
    private boolean match(Node node, String word, int index) {
        if (index == word.length()) return node.isWord;

        char c = word.charAt(index);
        if (c != '.') {
            if (node.next.get(c) == null)
                return false;
            return match(node.next.get(c), word, index + 1);
        } else {  // c == '.'
            for (char nextChar : node.next.keySet())
                if (match(node.next.get(nextChar), word, index + 1))
                    return true;
            return false;
        }
    }
}

时空复杂度分析

  • 时间复杂度:插入与插入字符串长度成正比,查询是指数级别。

  • 空间复杂度:和递归深度有关。

Leetcode 0386 字典序排数

题目描述:Leetcode 0386 字典序排数

在这里插入图片描述

分析

  • 本题的考点:深搜、trie

  • 本题可以将1~n这所有的数插入trie中,例如12插入trie则插入两个节点,即12。然后我们递归遍历这棵树,如果得到的节点对应的值小于等于n,则加入结果中。

  • 实际上,我们并不需要将trie建立出来。

代码

  • C++
class Solution {
public:
    vector<int> res;

    vector<int> lexicalOrder(int n) {
        for (int i = 1; i <= 9; i++) dfs(i, n);
        return res;
    }

    void dfs(int cur, int n) {
        if (cur <= n) res.push_back(cur);
        else return;
        for (int i = 0; i <= 9; i++) dfs(cur * 10 + i, n);
    }
};
  • Java
class Solution {

    List<Integer> res = new ArrayList<>();

    public List<Integer> lexicalOrder(int n) {
        for (int i = 1; i <= 9; i++) dfs(i, n);
        return res;
    }

    private void dfs(int cur, int n) {
        if (cur <= n) res.add(cur);
        else return;
        for (int i = 0; i <= 9; i++) dfs(cur * 10 + i, n);
    }
}

时空复杂度分析

  • 时间复杂度: O ( n × l o g ( n ) ) O(n \times log(n)) O(n×log(n))

  • 空间复杂度:考虑结果的存储, O ( n ) O(n) O(n)

Leetcode 0421 数组中两个数的最大异或值

题目描述:Leetcode 0421 数组中两个数的最大异或值

在这里插入图片描述

分析

  • 本题的考点:trie(字典树、前缀树)

  • 关于trie的讲解可以参考:trie(字典树、前缀树)。这里面就有对本题的讲解。

  • 这里题如果暴力求解的话,相当于让任意两个不同的数进行异或运算,然后记录最大的结果输出即可,伪码如下:

int res = 0;  // 最小是0
for (int i = 0; i < n; i++) {
    for (int j = 0; j < i; j++)
        res = max(res, a[i] ^ a[j]);
}
  • 我们分析内层循环,其实在寻找和a[i]异或值最大的另一个数据,我们可以使用字典树(trie)优化这一步,因为所有的数据范围在 [ 0 , 2 31 ) [0, 2^{31}) [0,231)之间,二进制位数最长是31位,我们可以将这31位二进制数看做一个字符串,最高位是字符串的第一个字符,然后将所有的这些字符串插入trie树中。

  • 这样操作之后,我们如何得到与某个数据A异或值最大的数呢?数据A可以看成一个31的二进制字符串,从左到右遍历这个字符串,假设当前考察的是字符u,则在trie树中我们应该走到!u的分支上(如果存在的话),这样异或值才能最大(贪心思想)。

代码

  • C++
class Solution {
public:
    vector<vector<int>> s;  // 两维,第一维是idx,第二维决定对应位是0还是1

    void insert(int x) {
        int p = 0;
        for (int i = 30; ~i; i--) {
            int u = x >> i & 1;
            if (!s[p][u]) s[p][u] = s.size(), s.push_back({0, 0});
            p = s[p][u];
        }
    }

    int query(int x) {
        int p = 0, res = 0;
        for (int i = 30; i >= 0; i--) {
            int u = x >> i & 1;
            if (s[p][!u]) p = s[p][!u], res = res * 2 + !u;
            else p = s[p][u], res = res * 2 + u;
        }
        return res ^ x;
    }

    int findMaximumXOR(vector<int>& nums) {
        s.push_back({0, 0});  // 创建根节点(空节点)
        int res = 0;
        for (auto x : nums) {
            res = max(res, query(x));
            insert(x);
        }
        return res;
    }
};
  • Java
class Solution {

    static class Node {
        Node[] son = new Node[2];
    }

    Node root = new Node();

    public int findMaximumXOR(int[] nums) {
        int res = 0;
        for (int x : nums) {
            res = Math.max(res, query(x));
            insert(x);
        }
        return res;
    }

    private void insert(int x) {
        Node p = root;
        for (int i = 30; i >= 0; i--) {
            int u = (x >> i) & 1;
            if (p.son[u] == null) p.son[u] = new Node();
            p = p.son[u];
        }
    }

    private int query(int x) {
        Node p = root;
        int res = 0;
        for (int i = 30; i >= 0; i--) {
            int u = (x >> i) & 1;
            if (p.son[u ^ 1] != null) {
                p = p.son[u ^ 1];
                res = res * 2 + 1 - u;
            } else {
                p = p.son[u];
                res = res * 2 + u;
            }
            // 数组模拟的话,刚开始没有元素时,p会一直是0,因此不会NPE
            // 这里不是数组模拟,因此刚开始trie中没有数据时,要让p一直指向root
            if (p == null) p = root;
        }
        return res ^ x;
    }
}

时空复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n为数组长度。
  • 空间复杂度: O ( n ) O(n) O(n)

Leetcode 1707 与数组中元素的最大异或值

题目描述:Leetcode 1707 与数组中元素的最大异或值

在这里插入图片描述

分析

在线做法

  • 因为nums中的数据是小于等于 1 0 9 10^9 109的,将每个数据看成二进制串,只需要0~29一共30个比特位即可存储每个数据。
  • 本题和LC421非常类似,区别在于异或的数据要小于某个数,为此每个节点需要记录额外的信息,这里记录以该节点为根的子的最小值min即可。
  • trie插入数据的时候,更新每个节点的min值,查询与x异或值最大的数时,需要判断两个条件:(1)异或的比特位尽量不同;(2)该节点存在小于给定值的值。

离线做法

  • 由于全部询问已经给出,我们不一定要按顺序回答询问,而是按照 m i m_i mi 从小到大的顺序回答。

  • 首先将数组 nums 从小到大排序,将询问按照 m i m_i mi 的大小从小到大排序。

  • 在回答每个询问前,将所有不超过 m i m_i minums 元素插入字典序中,由于 nums 已经排好序,我们可以维护一个指向 nums 数组元素的下标 idx,初始值为 0,每插入一个元素就将idx 加一。对于每个询问,我们可以不断插入满足 n u m s [ i d x ] ≤ m i nums[idx] \le m_i nums[idx]mi 的元素,直至不满足该条件或 idx 指向了数组末尾。 此时字典树中的元素就是 nums 中所有不超过 m i m_i mi 的元素然后就可以使用LC421求解。

  • 另外注意,由于queries 被排序了,我要记录每个查询原始对应的位置,因为最后的结果数据中的数据要按查询的顺序给出。

代码

  • C++
class Solution {
public:
    struct Node {
        Node *son[2];
        int min;  // 以该节点为根的子树存储的最小值
        Node() {
            son[0] = son[1] = NULL;
            min = INT_MAX;
        }
    } *root;

    void insert(int val) {
        auto p = root;
        p->min = min(p->min, val);
        for (int i = 29; i >= 0; i--) {
            int u = val >> i & 1;
            if (!p->son[u]) p->son[u] = new Node();
            p = p->son[u];
            p->min = min(p->min, val);
        }
    }

    // 返回数值不大于m的与x异或值最大的结果
    int query(int x, int m) {
        auto p = root;
        if (p->min > m) return -1;
        int res = 0;
        for (int i = 29; i >= 0; i--) {
            int u = x >> i & 1;
            if (p->son[!u] && p->son[!u]->min <= m) p = p->son[!u], res = res * 2 + !u;
            else p = p->son[u], res = res * 2 + u;
        }
        return res ^ x;
    }

    vector<int> maximizeXor(vector<int>& nums, vector<vector<int>>& queries) {

        root = new Node();
        for (int x : nums) insert(x);
        vector<int> res;
        for (int i = 0; i < queries.size(); i++) {
            int x = queries[i][0], m = queries[i][1];
            res.push_back(query(x, m));
        }
        return res;
    }
};
  • Java
class Solution {

    static class Node {
        Node[] son = new Node[2];
        int min = Integer.MAX_VALUE;
    }

    Node root = new Node();

    private void insert(int val) {
        Node p = root;
        p.min = Math.min(p.min, val);
        for (int i = 29; i >= 0; i--) {
            int u = val >> i & 1;
            if (p.son[u] == null) p.son[u] = new Node();
            p = p.son[u];
            p.min = Math.min(p.min, val);
        }
    }

    private int query(int x, int m) {
        Node p = root;
        if (p.min > m) return -1;
        int res = 0;
        for (int i = 29; i >= 0; i--) {
            int u = x >> i & 1;
            if (p.son[1 ^ u] != null && p.son[1 ^ u].min <= m) {
                p = p.son[1 ^ u];
                res = res * 2 + 1 - u;
            } else {
                p = p.son[u];
                res = res * 2 + u;
            }
        }
        return res ^ x;
    }

    public int[] maximizeXor(int[] nums, int[][] queries) {

        for (int x : nums) insert(x);
        int[] res = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int x = queries[i][0], m = queries[i][1];
            res[i] = query(x, m);
        }
        return res;
    }
}
  • Python
# TLE
class Solution:
    class Node:
        def __init__(self):
            self.son = [None] * 2
            self.min = int(2e9)

    def maximizeXor(self, nums: List[int], queries: List[List[int]]) -> List[int]:
        root = self.Node()
        for x in nums:
            self.insert(root, x)
        res = []
        for i in range(len(queries)):
            x = queries[i][0]
            m = queries[i][1]
            res.append(self.query(root, x, m))
        return res
    
    def insert(self, root, val):
        p = root
        p.min = min(p.min, val)
        for i in range(29, -1, -1):
            u = val >> i & 1
            if p.son[u] == None:
                p.son[u] = self.Node()
            p = p.son[u]
            p.min = min(p.min, val)
    
    def query(self, root, x, m):
        p = root
        if p.min > m:
            return -1
        res = 0
        for i in range(29, -1, -1):
            u = x >> i & 1
            if p.son[1 ^ u] and p.son[1 ^ u].min <= m:
                p = p.son[1 - u]
                res = res * 2 + 1 - u
            else:
                p = p.son[u]
                res = res * 2 + u
        return res ^ x

时空复杂度分析

  • 时间复杂度: O ( n + m ) O(n + m) O(n+m)n为数组nums长度,mqueries长度。

  • 空间复杂度: O ( n ) O(n) O(n)


AcWing相关题目

  • 给定一个数组(元素非负),返回异或值最大的两个元素异或的结果。AcWing 143. 最大异或对

  • 给定一个数组(元素非负),请你在该整数序列中找出一个连续的非空子序列,使得子序列中元素的异或和能够最大。如果存在多个这样的序列,返回区间右端点最小的那个。AcWing 1414. 牛异或

  • 给定一个数组(元素非负),请在所有长度不超过 M连续子数组(子数组可以为空)中,找出子数组异或和的最大值。AcWing 3485. 最大异或和

最长公共前缀是指一组字符串中,所有字符串都有的最长的前缀。根据提供的代码,可以看出这是一个用C语言实现的求解最长公共前缀的函数。函数的输入是一个字符串数组和数组的大小,输出是一个字符指针,指向最长公共前缀的起始位置。 这个函数的实现思路是,首先取数组中第一个字符串的第一个字符作为初始比较字符,然后遍历数组中的每个字符串的相同位置的字符,如果有不相同的字符,则退出循环。如果所有字符串的相同位置的字符都相同,则将初始比较字符更新为下一个位置的字符,继续比较下一位置的字符,直到遍历完所有字符串或者找到不相同的字符。最后,将最长公共前缀的末尾字符置为'\0',返回最长公共前缀的起始位置。 这个函数的时间复杂度是O(n*m),其中n是字符串数组的大小,m是最长公共前缀的长度。 #### 引用[.reference_title] - *1* *2* [力扣:最长公共前缀(详解)](https://blog.csdn.net/weixin_73142957/article/details/129778838)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [力扣专题——14. 最长公共前缀——最长公共前缀C语言解法](https://blog.csdn.net/qq_42479987/article/details/116953103)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值