Given a list of phone numbers, determine if it is consistent in the
sense that no number is the prefix of another. Let’s say the phone
catalogue listed these numbers:
Emergency 911
Alice 97 625 999
Bob 91 12 54 26
In this case, it’s not possible to call Bob, because the central would
direct your call to the emergency line as soon as you had dialled the
first three digits of Bob’s phone number. So this list would not be
consistent.
Input specifications
The first line of input gives a single integer, 1 ≤ t ≤ 40, the number of test cases. Each
test case starts with n, the number of phone numbers, on a separate line, 1 ≤ n ≤ 10000.
Then follows n lines with one unique phone number on each line. A phone number is a
sequence of at most ten digits.
Output specifications
For each test case, output “YES” if the list is consistent, or “NO” otherwise.
Sample input
2
3
911
97625999
91125426
5
113
12340
123440
12345
98346
Output for sample input
NO
YES
题目描述的简要概括:
有n个数字串, 检查这些串中是否存在一个字符串是另一个字符串的前缀。
一开始用枚举法 O(logn)情况下果断超时。
然后根据题解 , 将所有字符串排序, 然后判断前一个是否为后一个的前缀, 继续超时。
网上查找后,据说这是标准的trie树 ,也成为字典树或前缀树。
实现方式是先建立字典树:
node 类型 包含两个成员变量: next【10】 保存 下个数字分别为0-9 对应的下一个节点。 flag 标注这里是否为某个单词的结尾。
trie数组 包含所有node, 创建时由cnt 记录当前用到第几个节点。
当加入一个数字串num时,检查当前数字i。 起始位置为 trie【0】, 如果当前位置的next【10】中 ,num[i]这个值存在, 说明num的前i个数字已被字典树包含,继续检查下一个数字。 若字典树在当前位置的next【10】 中没有num【i】 ,则创建之。到num结尾时,设当前位置flag为1 表明这是某个单词的个结尾
模块如下:
void Insert(char *s){
int p = 0;
while(*s){
if(tree[p].next[*s - '0'] == -1){
tree[p].next[*s - '0'] = cnt;
cnt++;
}
p = tree[p].next[*s - '0'];
s++;
}
tree[p].flag = 1;
}
字典树创建完成后, 将数字串依次代入检查:
int Query(char *s){
int p = 0;
while(*s){
if(tree[p].flag == 1){
return 0;
}
else{
p = tree[p].next[*s - '0'];
}
s++;
}
return 1;
}
注意这里先判断的 p是不是结尾 ,然后更新的p值,最后后移s。 当p更新到s结尾时,s已经超出,循环结束, 将不再给处在结尾的p判断flag的机会。 也就是说,这里不会把一个数字串自己判断为自己的前缀。
代码:
#include <stdio.h>
#include <string.h>
int cnt;
char phonum[10005][15];
struct Node{
int next[10];
int flag;
} tree[100000];
void Insert(char *s){
int p = 0;
while(*s){
if(tree[p].next[*s - '0'] == -1){
tree[p].next[*s - '0'] = cnt;
cnt++;
}
p = tree[p].next[*s - '0'];
s++;
}
tree[p].flag = 1;
}
int Query(char *s){
int p = 0;
while(*s){
if(tree[p].flag == 1){
return 0;
}
else{
p = tree[p].next[*s - '0'];
}
s++;
}
return 1;
}
int main (){
int i,T,n;
int flag;
scanf("%d",&T);
while(T--){
flag = 1;
cnt = 1;
for(i = 0;i < 100000;i++){
memset(tree[i].next,-1,sizeof(tree[i].next));
tree[i].flag = 0;
}
for(i = 0 ;i < 10005;i++){
memset(phonum[i],'\0',sizeof(phonum[i]));
}
scanf("%d",&n);
for(i = 0;i < n;i++){
scanf("%s",phonum[i]);
Insert(phonum[i]);
}
for(i = 0;i < n;i++){
if(Query(phonum[i]) == 0){
flag = 0;
break;
}
}
if(flag == 0){
printf("NO\n");
}
else{
printf("YES\n");
}
}
return 0;
}
另外, 还有一种效率更高的做法。
该做法不是先建立好整个字典树再检查, 而是再插入每个数字串时,就判断树中是否有它的前缀,以及他是否为已经存在于树中的某个数字串的前缀,代码如下:
#include<iostream>
using namespace std;
char num[12];
int trie[100000][10];
int last;
int insert(int cnt,int where)
{
int i,w;
if (where>last)
{
last++;
for (i=0;i<=9;i++)
trie[last][i]=-1;
}
w=trie[where][num[cnt]-48];
if (w==-2) return 0;
if (w==-1) {
trie[where][num[cnt]-48]=num[cnt+1]?last+1:-2;
w=last+1;
}
return num[cnt+1]? insert(cnt+1,w): w==last+1;
}
int main(void)
{
// freopen("1522.txt","r",stdin);
int t,n;
scanf("%d",&t);
while (t--)
{
int i,pre=0;
last=-1;
scanf("%d",&n);
for (i=0;i<n;i++)
{
scanf("%s",num);
if (!pre) pre|=!insert(0,0);
}
printf("%s\n",pre?"NO":"YES");
}
return 0;
}
这个算法还没看太明白 以后再慢慢学习