计蒜客 习题:糟糕的bug(Trie树)

2 篇文章 0 订阅
2 篇文章 0 订阅

题目来源:计蒜客https://www.jisuanke.com/minicourse/812/42071

 

蒜头君作为蒜厂的工程师,在开发网站时不小心写出了一个 Bug:当用户输入密码时,如果既和自己的密码一致,也同时是另一个用户密码的 前缀 时,用户会跳转到 404 页。

然而蒜头君坚称:我们的用户那么少,怎么可能触发这个 Bug……

机智的你,能不能帮蒜头君确认一下这个 Bug 到底会不会触发呢?

样例输入

第一行输入一个整数n(1≤n≤233333),表示蒜厂网站的用户数。接下来一共 n 行,每行一个由小写字母a-z组成的字符串,长度不超过 10,表示每个用户的密码。蒜厂的数据库容量太小,所有密码长度加起来小于 466666。

样例输出

如果触发了 Bug 则输出一行Bug!,否则输出一行Good Luck!

样例输入1

3
abc
abcdef
cdef

样例输出1

Bug!

样例输入2

3
abc
bcd
cde

样例输出2

Good Luck!

 

题目类型就是串匹配,找前缀,多串情况下,很应该想到前缀树,也称为Trie树。

这里首先需要把所有串都读下来插入之后再进行匹配,因为你不能确定一定是前面的是后面的前缀。

对cnt处理的时候 也不能像这个Trie树处理一样,即:

需要注意的是,本题这里需要找一个串是否为另一个串的前缀,与前缀树的一个小小区别是,前缀树会统计一个串的每个前缀出现次数,而这里只需要统计每个串出现的次数。

那么如何判断一个串是不是另一个串的前缀呢?这里就用到反向比较的方法了。

例如:字符串:abc,和字符串abcd。再Trie树中,abc对应的出现次数为1,在搜索abcd的过程中会发现abc出现过,即存在一个题目所述的“bug!”。

另外,思考一下,为什么不会因为找到自己而返回True,导致出现bug呢?

注意下在Trie树中存储字符串出现次数的方法,代码第33行存储一个字符串出现的次数时,是字符串遍历结束,最后一个字母所指向的存储位置,而在查询过程中代码第39行的那个“p”其实是字符串中倒数第二个字母所指向的存储位置,因此,是不会遍历到自己的。

此时就引发了一个思考,如果有两个串是一致的,那么该代码会引发一个什么答案呢?

尝试一下,就会发现,该样例不成为为题目中所述的“Bug!”,但该样例是否应该触发“Bug!”呢?严格来说是应该触发的,即自身属于自身的一个前缀,当出现两个人的密码一致时,是应该触发一个“Bug!”,但这份代码依然通过了所有测试样例,由此可见,后台应该没有考虑这种情况。

但如果需要处理这种情况,该如何应对呢?

也很简单,判断某个字符串是否出现过超过一次即可。

怎么判断呢?挨个遍历比较?O(n^2)的复杂度。

那有什么高效算法呢?前缀树呀,代码33行一旦该值超过1,就说明该串出现了两次,直接返回触发“Bug!”

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define MAX_N 2333430
#define MAX_C 26

struct Trie{
	int *ch[MAX_N];
	int tot;
	int cnt[MAX_N];

	Trie() {
		tot = 0;
		memset( ch, 0, sizeof(ch) );
		memset( cnt, 0, sizeof(cnt) );
	}
	
	void insert( const char* str ) {
		int p = 0;
		for ( int i = 0 ; str[i] ; ++ i ) {
			if ( ch[p] == NULL ) {
				ch[p] = new int[MAX_C];
				memset( ch[p], -1, sizeof(int)*MAX_C );
			}
			if ( ch[p][str[i]-'a'] == -1 ) {
				ch[p][str[i]-'a'] = ++tot;
			}
			p = ch[p][str[i]-'a'];
		}
		cnt[p] ++;// 这里记录完整的串 而不是前缀 
	}
	
	bool find( const char* str ) {
		int p = 0;
		for ( int i = 0 ; str[i] ; ++ i ) {
			if ( cnt[p] != 0 ) return true; 
			if ( ch[p] == NULL ) return false;
			if ( ch[p][str[i]-'a'] == -1 ) return false;
			p = ch[p][str[i]-'a'];
		}
		return false;
	}
};


char s[MAX_N][15];
Trie trie;
int main() 
{
	int n;
	scanf( "%d", &n );
	getchar();
	bool ans = false;
	for ( int i = 0 ; i < n ; ++ i ) {
		scanf( "%s", s[i] );
		getchar();
		trie.insert(s[i]);
	}
	for ( int i = 0 ; i < n ; ++ i ) {
		if ( trie.find(s[i]) ) {
			ans = true;
			break;
		}
	}
	if ( ans ) {
		puts("Bug!");
	} else {
		puts("Good Luck!");
	}
	return 0;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值