数据结构 之 前缀树

前言

前缀树是一种利于高效地存储和检索字符串数据集中的键,适合用于表示有基础变量复合出来的复杂变量,如26个字符复合出来的单词,实现快速搜索和插入,以及前缀查询。

一、案例

在这里插入图片描述

二、题解

package com.xhu.offer.offerII;

//实现前缀树
public class Trie {
    TreeNode root;

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

    /**
     * Inserts a word into the trie.
     */
    public void insert(String word) {
        char[] chs = word.toCharArray();
        TreeNode p = root;

        for (char ch : chs) {
            int idx = ch - 'a';
            TreeNode n = p.next[idx];

            if (n == null) p.next[idx] = new TreeNode();

            p = n;
        }
        p.isEnd = true;
    }

    /**
     * Returns if the word is in the trie.
     */
    public boolean search(String word) {
        char[] chs = word.toCharArray();
        TreeNode p = root;
        for (char ch : chs) {
            int idx = ch - 'a';
            TreeNode n = p.next[idx];
            if (n == null) return false;

            p = n;
        }
        return p.isEnd;
    }

    /**
     * Returns if there is any word in the trie that starts with the given prefix.
     */
    public boolean startsWith(String prefix) {
        char[] chs = prefix.toCharArray();
        TreeNode p = root;
        for (char ch : chs) {
            int idx = ch - 'a';
            TreeNode n = p.next[idx];
            if (n == null) return false;

            p = n;
        }
        return true;
    }

    //只有小写字母,定义前缀树的数据结构
    public class TreeNode {
        boolean isEnd;

        TreeNode[] next;

        public TreeNode() {
            next = new TreeNode[26];
        }
    }
}

总结

1)第一次见前缀树结构,适用于固定的单一变量复合出复杂变量问题。

参考文献

[1] LeetCode 原题
[2] LeetCode 用户评论

附录

1、替换单词

在这里插入图片描述

package com.xhu.offer.offerII;

import java.util.Iterator;
import java.util.List;

//替换单词
public class ReplaceWords {
    //构建词根树
    TreeNode root = new TreeNode();

    public String replaceWords(List<String> dictionary, String sentence) {
        buildTrie(dictionary);
        //替换单词
        StringBuilder sb = new StringBuilder();
        String[] strs = sentence.split(" ");
        for (String str : strs) {
            char[] chs = str.toCharArray();

            TreeNode p = root;
            int count = 0;
            for (char ch : chs) {
                int idx = ch - 'a';
                TreeNode n = p.next[idx];
                if (n == null || p.isEnd) break;
                count++;
                p = p.next[idx];
            }
            sb.append(p.isEnd ? str.substring(0, count) : str).append(" ");
        }
        return sb.toString().trim();
    }


    private void buildTrie(List<String> dic) {
        Iterator<String> iterator = dic.iterator();
        while (iterator.hasNext()) {
            String s = iterator.next();
            char[] chs = s.toCharArray();
            TreeNode p = root;
            for (char ch : chs) {
                int idx = ch - 'a';
                TreeNode n = p.next[idx];
                if (n == null) p.next[idx] = new TreeNode();
                p = p.next[idx];
            }
            p.isEnd = true;
        }
    }

    public class TreeNode {
        boolean isEnd;
        TreeNode[] next = new TreeNode[26];
    }

}

2、神奇的字典

在这里插入图片描述

package com.xhu.offer.offerII;

import java.util.ArrayList;
import java.util.List;

//神奇的字典
public class MagicDictionary {
    /**
     * Initialize your data structure here.
     */
    public MagicDictionary() {

    }

    /**
     * 构造前缀树
     *
     * @param dictionary 前缀树构造所需单词
     */
    public void buildDict(String[] dictionary) {
        for (String s : dictionary) {
            char[] chs = s.toCharArray();
            TreeNode p = root;

            for (char ch : chs) {
                int idx = ch - 'a';

                if (p.next[idx] == null) p.next[idx] = new TreeNode();

                p = p.next[idx];
            }
            p.isEnd = true;
        }
    }

    TreeNode root = new TreeNode();

    public boolean search(String searchWord) {
        return recursion(root, searchWord, 0, 0);
    }

    /**
     * DFS
     *
     * @param root       表示上一次比较时所用节点,即父节点,递归口通过判断父节点是否为end节点来确定是否完全匹配。
     * @param searchWord 表示需要去DFS匹配的字符串
     * @param i          该比searchWord的第几个字符了
     * @param count      表示前面已经有几个字符不同了
     * @return 只要匹配成功一次就返回true,即成功匹配,否则false,for中的flag为false表示继续DFS,for外的false表示DFS完也匹配不成功
     */
    private boolean recursion(TreeNode root, String searchWord, int i, int count) {
        if (count > 1 || root == null) return false;
        if (i == searchWord.length()) {
            if (root.isEnd && count == 1) return true;
            else return false;
        }
        for (int j = 0; j < 26; j++) {
            boolean flag = false;
            int idx = searchWord.charAt(i) - 'a';

            if (idx != j) flag = recursion(root.next[j], searchWord, i + 1, count + 1);
            else flag = recursion(root.next[j], searchWord, i + 1, count);
            if (flag) return true;
        }
        return false;
    }


    public class TreeNode {
        boolean isEnd;
        TreeNode[] next = new TreeNode[26];
    }
}

//使用list,找同等长度的字符串拿去作比较,修改修改一次两者相等,则true,否则false。
//这里判断是否修改一次相等,就对比有多少字符不相等即可。
class MagicDictionary2 {
    List<String> cache = new ArrayList<>();

    /**
     * Initialize your data structure here.
     */
    public MagicDictionary2() {

    }

    public void buildDict(String[] dictionary) {
        for (String s : dictionary) cache.add(s);
    }

    public boolean search(String searchWord) {
        int size = cache.size();

        for (String s : cache) {
            if (s.length() != searchWord.length()) continue;
            if (find(s, searchWord)) return true;
        }
        return false;
    }

    //注:只修改一次,直接比较字符不等的时候有多少,不用递归+for那么麻烦。
    private boolean find(String s, String searchWord) {
        int count = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != searchWord.charAt(i)) count++;
            if (count > 1) return false;
        }

        return count == 1;
    }

}

3、最短单词编码之逆前缀树

掌握好前缀树的各个细节,才能举一反三,采用逆前缀树来解决最短单词编码。
在这里插入图片描述

package com.xhu.offer.offerII;

//最短的单词编码
public class MinimumLengthEncoding {
    //倒装的前缀树即可,然后统计每条路径长度之和 + 路径条数(需要多少#)
    public int minimumLengthEncoding(String[] words) {
        //建树
        buildTree(words);
        //统计路径
        return countPath(root)[0];
    }


    private int[] countPath(TreeNode root) {

        if (root.next == null) {
            return new int[]{1, 1};
        }
        int res = 0, count = 0;
        for (int i = 0; i < 26; i++) {
            if (root.next[i] != null) {
                int[] r = countPath(root.next[i]);
                count += r[1];
                res += r[0];
            }
        }
        return new int[]{res + count, count};
    }

    TreeNode root = new TreeNode();

    private void buildTree(String[] words) {
        for (String word : words) {
            char[] chs = word.toCharArray();
            TreeNode p = root;
            for (int i = chs.length - 1; i >= 0; i--) {
                int idx = chs[i] - 'a';
                if (p.next == null) p.next = new TreeNode[26];
                if (p.next[idx] == null) p.next[idx] = new TreeNode();
                p = p.next[idx];
            }
        }
    }

    public class TreeNode {
        TreeNode[] next;
    }
}

4、单词之和

在这里插入图片描述

package com.xhu.offer.offerII;

//单词之和
public class MapSum {
    //前缀树

    /**
     * Initialize your data structure here.
     */
    public MapSum() {

    }

    TreeNode root = new TreeNode();

    public void insert(String key, int val) {
        char[] chs = key.toCharArray();
        TreeNode p = root;

        for (char ch : chs) {
            int idx = ch - 'a';

            if (p.next == null) p.next = new TreeNode[26];
            if (p.next[idx] == null) p.next[idx] = new TreeNode();

            p = p.next[idx];
        }
        p.isEnd = true;
        p.val = val;
    }


    public int sum(String prefix) {
        char[] chs = prefix.toCharArray();

        TreeNode p = root;
        for (char ch : chs) {
            int idx = ch - 'a';

            if (p.next == null || p.next[idx] == null) return 0;

            p = p.next[idx];
        }
        int[] total = new int[]{0};
        order(p, total);

        return total[0];
    }

    private void order(TreeNode p, int[] total) {
        if (p.isEnd) {
            total[0] += p.val;
        }
        if (p.next == null) return;

        for (int i = 0; i < 26; i++) {
            if (p.next[i] != null) order(p.next[i], total);
        }
    }

    public class TreeNode {
        boolean isEnd;
        int val;
        TreeNode[] next;
    }
}

5、最大的异或

在这里插入图片描述

package com.xhu.offer.offerII;


//最大的异或
public class FindMaximumXOR {
    TreeNode root = new TreeNode();

    public int findMaximumXOR(int[] nums) {
        //建树
        buildTree(nums);
        //寻找最大解
        int res = 0;
        for (int num : nums) {
            int t = find(num);
            res = res < t ? t : res;
        }
        return res;
    }


    private int find(int num) {
        int count = 30;
        TreeNode p = root;
        int res = 0;
        while (count >= 0) {
            int m = num >>> count & 1;
            boolean f1 = m == 1 && p.right != null, f2 = m == 0 && p.left != null;

            if (f1 || f2) res |= 1 << count;

            if (p.right != null && (m == 1 || p.left == null)) p = p.right;
            else p = p.left;
            count--;
        }
        return res;
    }

    private void buildTree(int[] nums) {
        for (int num : nums) {
            int count = 30;
            TreeNode p = root;

            while (count >= 0) {
                int m = num >>> count-- & 1;
                boolean f1 = m == 1 && p.left == null, f2 = m == 0 && p.right == null;
                if (f1) p.left = new TreeNode();
                if (f2) p.right = new TreeNode();

                p = m == 1 ? p.left : p.right;
            }
        }
    }

    public class TreeNode {
        TreeNode left;//1
        TreeNode right;//0
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值