Leetcode字符流

本文介绍如何利用前缀树数据结构解决LeetCode中的字符流问题。通过建立前缀树,在字符流中逐个添加字符,实时检测当前的字符序列是否为给定单词数组中的后缀。文章提供了暴力解法和优化解法,优化解法使用前缀树提高查询效率。

题目描述

设计一个算法:接收一个字符流,并检查这些字符的后缀是否是字符串数组 words 中的一个字符串。

例如,words = [“abc”, “xyz”] 且字符流中逐个依次加入 4 个字符 ‘a’、‘x’、‘y’ 和 ‘z’ ,你所设计的算法应当可以检测到 “axyz” 的后缀 “xyz” 与 words 中的字符串 “xyz” 匹配。

按下述要求实现 StreamChecker 类:

StreamChecker(String[] words) :构造函数,用字符串数组 words 初始化数据结构。
boolean query(char letter):从字符流中接收一个新字符,如果字符流中的任一非空后缀能匹配 words 中的某一字符串,返回 true ;否则,返回 false。

示例:

输入:
[“StreamChecker”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”]
[[[“cd”, “f”, “kl”]], [“a”], [“b”], [“c”], [“d”], [“e”], [“f”], [“g”], [“h”], [“i”], [“j”], [“k”], [“l”]]
输出:
[null, false, false, false, true, false, true, false, false, false, false, false, true]

解释:
StreamChecker streamChecker = new StreamChecker([“cd”, “f”, “kl”]);
streamChecker.query(“a”); // 返回 False
streamChecker.query(“b”); // 返回 False
streamChecker.query(“c”); // 返回n False
streamChecker.query(“d”); // 返回 True ,因为 ‘cd’ 在 words 中
streamChecker.query(“e”); // 返回 False
streamChecker.query(“f”); // 返回 True ,因为 ‘f’ 在 words 中
streamChecker.query(“g”); // 返回 False
streamChecker.query(“h”); // 返回 False
streamChecker.query(“i”); // 返回 False
streamChecker.query(“j”); // 返回 False
streamChecker.query(“k”); // 返回 False
streamChecker.query(“l”); // 返回 True ,因为 ‘kl’ 在 words 中

提示:

1 <= words.length <= 2000
1 <= words[i].length <= 200
words[i] 由小写英文字母组成
letter 是一个小写英文字母
最多调用查询 4 * 104 次

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/stream-of-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析

解法一:暴力法
使用StringBuffer构造不断加入一个letter字符后的字符串
然后用String类的endswith()方法判断字符串是否以words字符串结尾

代码

class StreamChecker {
	String[] strings=null;
	StringBuffer sb = null;
	public StreamChecker(String[] words) {
		this.strings=words;
		this.sb = new StringBuffer();
	}

	public boolean query(char letter) {
		sb.append(letter);
		String collect = sb.toString();
		for(String s:strings){
			if(collect.endsWith(s)){
				return true;
			}
		}
		return false;
	}
}

解放二:前缀树
使用StringBuffer构造不断加入一个letter字符后的字符串
将words数组的字符串反转后插入前缀树,然后反转StringBuffer字符串与前缀树中相比较

代码

class Trie{
	/**
	 * Trie树节点
	 * 假设我们只做26个小写字母下的匹配
	 */
	public static class Node{
		//当前节点值
		private char value;
		//当前节点的孩子节点
		private Node[] childNode;
		//标志当前节点是否是某单词结尾
		private boolean isTail;
		public Node(char value) {
			this.value = value;
		}
	}
	//初始化	
	Node root;
	public void init() {
		root = new Node('\0');
		root.childNode = new Node[26];
	}
	/**
	 * 将当前串插入字典树
	 * @param chars
	 */
	public void insertStr(char[] chars) {
		//首先判断首字符是否已经在字典树中,然后判断第二字符,依次往下进行判断,找到第一个不存在的字符进行插入孩节点
		Node p = root;
		//表明当前处理到了第几个字符
		int chIndex = 0;
		while (chIndex < chars.length) {
			while (chIndex < chars.length && null != p) {
				Node[] children = p.childNode;
				boolean find = false;
				//遍历子节点,确定该字符是否是其中一个子节点的value
				for (Node child : children) {
					if (null == child) {continue;}
					if (child.value == chars[chIndex]) {
						//当前字符已经存在,不需要再进行存储
						//从当前节点出发,存储下一个字符
						p = child;
						++ chIndex;
						find = true;
						break;
					}
				}
				if (Boolean.TRUE.equals(find)) {
					//在孩子中找到了 不用再次存储
					break;
				}
				//如果把孩子节点都找遍了,还没有找到这个字符,直接将这个字符加入当前节点的孩子节点
				Node node = new Node(chars[chIndex]);
				node.childNode = new Node[26];
				children[chars[chIndex] - 'a'] = node;
				p = node;
				++ chIndex;
			}
		}
		//字符串中字符全部进入tire树中后,将最后一个字符所在节点标志为结尾节点
		p.isTail = true;
	}
	public boolean query(StringBuilder s){
		Node p = root;
		for(int i=s.length()-1,j=0;i>=0&&j<201;--i,++j){
            int count=0;
            //count计算一个字符的比较次数,如果count=26代表比较完26个子节点都没找到,返回false
			char idex = s.charAt(i);
			Node[] children = p.childNode;
			//遍历子节点
			for(Node child:children){
				if(child==null){
                    count++;
					continue;
				}
				//发现字符与一个子节点的value相等,则定位到下个字符与该子节点的子节点进行比较
				if(child.value==idex){
					p=child;
					//发现是一个word[]中的字符串,匹配成功返回true
					if(p.isTail==true){
						return true;
					}
					break;//跳出当前的子节点遍历
				}else{
                    count++;
                }
			}
            if(count==26){
				return false;
			}
		}
		return false;
	}
}

class StreamChecker {
    private StringBuilder sb = new StringBuilder();
    private Trie trie = new Trie();

    public StreamChecker(String[] words) {
        trie.init();
        for (String w : words) {
            StringBuffer sb = new StringBuffer(w);
			trie.insertStr(sb.reverse().toString().toCharArray());
        }
    }

    public boolean query(char letter) {
        sb.append(letter);
        return trie.query(sb);
    }
}

关于前缀树请参考链接: link

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值