前缀树笔记

1. 前言

发现笔试题经常爱考前缀树。

每次慢慢回忆,再慢悠悠的写代码,时间根本来不及。

需要总结一下记忆方法,目标是以后碰到前缀树的题能上手就来。

记忆点一:图
前缀树
记忆点二:

  1. 边表示字符,结点对应 isEnd 和 count 信息。
  2. 从结点到根只有一条路径,表示一个字符串前缀。

记忆点三:

class Trie {
	final int max = 1000;
	int[][] tree = new tree[max][26]; // 一行表示一个结点;孩子表示法
	int[] count = new int[max];
	boolean[] isEnd = new boolean[max];
	int last;

	public void insert(String word) {
		int p = 0;
		for(int i = 0; i < word.length(); ++i) {
			int c = word.charAt(i)-'a';
			if(tree[p][c] == 0) tree[p][c] = ++last; // 分配结点
			p = tree[p][c];
			count[p]++; // 更新结点对应的计数信息
		}
		isEnd[p] = true; // 更新 isEnd 信息
	}
}

原来总结知识点的时候,总想总结的很全,没有纰漏。
结果就是总结的很复杂,抓不住重点,华而不实。
大脑抗拒复杂的东西,总结的再全再复杂,只能在纸上,不能进脑子里,都是白搭。

所以别看前缀树那么多操作,记住插入即可!

2. 阿里笔试题

题目描述:
输入法里预存了一些词汇。在我们输入一个单词时,我们有两种操作可选:

  1. 花费 1 个单位的时间输入一个字符。
  2. 在我们输入时,输入法会以你的输入作为前缀去匹配它的预存词汇,并给出匹配的 m 个单词,你可以花费 m 个单位的时间选择任一单词代替现有输入。

问输入一个单词所花费的最短时间是多少。

输入描述:
第一行两个整数 n,p,后面每一行表示一个单词,前 n 行为输入法的预存词汇,后 p 行表示 p 个单词输入。

输出描述:
输出 p 行,表示 p 个单词输入对应的最短时间。

例如:
输入,
3 1
nt
nowcoder
nc
nowcoderhhh

输出,
6

输出解释:输入 no 花费 2 个单位时间,只匹配一个词 nowcoder,花费 1 个单位时间用 nowcoder 替换 no,最后花 3 个单位时间输入后面的 hhh。共花费 6 个单位时间。

代码求解:

import java.util.*;

public class Main{
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt(), p = sc.nextInt();
		
		Trie trie = new Trie();
		for(int i = 0; i < n; ++i) {
			trie.insert(sc.next());
		}
		
		for(int i = 0; i < p; ++i) {
			char[] word = sc.next().toCharArray();
			// 首先判断前缀树里是否有 word 的前缀,有的话最长前缀有多长
			int len = trie.longestPrefix(word);
			if(len < 0) {
				System.out.println(word.length);
				continue;
			}
			int min = len+1;
			// 得到最长前缀的最短时间
			for(int j = 0; j <= len; ++j) {
				int temp = j+1+trie.countPrefix(word, j);
				if(temp < min) min = temp;
			}
			System.out.println(min+(word.length-1-len));
		}
	}
}


class Trie{
	final int max = 1000;
	int[][] tree = new int[max][26];
	int[] count = new int[max];
	boolean[] isEnd = new boolean[max];
	int last;
	
	public void insert(String word) {
		int p = 0;
		for(int i = 0; i < word.length(); ++i) {
			int c = word.charAt(i) - 'a';
			if(tree[p][c] == 0) tree[p][c] = ++last;
			p = tree[p][c];
			count[p]++;
		}
		isEnd[p] = true;
	}
	
	public int countPrefix(char[] prefix, int j) {
		int p = 0;
		for(int i = 0; i <= j; ++i) {
			int c = prefix[i] - 'a';
			if(tree[p][c] == 0) return 0;
			p = tree[p][c];
		}
		return count[p];
	}
	
	public int longestPrefix(char[] word) {
		int p = 0;
		for(int i = 0; i < word.length; ++i) {
			int c = word[i] - 'a';
			if(tree[p][c] == 0 && isEnd[p]) return i-1;
			p = tree[p][c];
		}
		return -1;
	}
}

3. 附录:前缀树的更常规实现

class Trie {
    class TrieNode{
        TrieNode[] links = new TrieNode[26];
        boolean isEnd;
    }

    TrieNode root;

    /** Initialize your data structure here. */
    public Trie() {
        root = new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode p = root;
        for(int i = 0; i < word.length(); ++i){
            char c = word.charAt(i);
            if(p.links[c-'a'] == null) p.links[c-'a'] = new TrieNode();
            p = p.links[c-'a'];
        }
        p.isEnd = true;
    }

    private TrieNode searchPrefix(String prefix){
        TrieNode p = root;
        for(int i = 0; i < prefix.length(); ++i){
            char c = prefix.charAt(i);
            if(p.links[c-'a'] == null) return null;
            p = p.links[c-'a'];
        }
        return p;
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        TrieNode p = searchPrefix(word);
        return p == null ? false : p.isEnd;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        TrieNode p = searchPrefix(prefix);
        return p != null;
    }
}

参见 leetcode 题目:https://leetcode-cn.com/problems/implement-trie-prefix-tree/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值