原题链接
http://acm.hdu.edu.cn/showproblem.php?pid=1671
问题分析
该题需要我们判断字符串中是否有字符串是其他字符串的前缀。本题中使用了字典树,字典树结构对处理字符串前缀问题非常方便。主要考虑一下三种情况:
(1)以前插入的字符串是当前字符串的前缀:比如,之前向字典树中插入字符串“911”,当前插入的字符串为“9110”。当前字符串“9110”插入第三个字符‘1’的时候,发现该节点的v值为’1’,表示某个字符串的结尾。直接返回0,不再插入字符(也不会存在新建节点),输出NO。
(2)当前插入的字符串为以前插入的字符串的前缀:比如,以前插入字符串“9110”,当前插入的字符串为“911”。此时,设置一个变量isOk,初始值为0,只有当新建节点的时候,isOk才会为1。很明显,当前插入“911”并不需要新建节点,知道插完最后一个字符’1’,isOk的值还为0,此时返回isOk,表示不一致,输出NO。
(3)不存在前缀,插入字符串过程中新建节点,isOk=1,输出YES。
注意:一个案例判断完毕后要释放这个案例的字典树占用的内存,否则,会超出内存范围。
AC代码
# include<stdio.h>
# include<string.h>
# include<malloc.h>
# define MAX 10
typedef struct Trie{
struct Trie * next[MAX];
char v;//若为'1',表示为字符串的最后一个字符,否则为字符串的中间字符
}Trie;
int isConsistent(Trie ** root,char num[]);
void myfree(Trie ** root);
int main()
{
char num[11];
int t,n,i;
Trie * root;
scanf("%d",&t);
while(t--)
{
root=NULL;
int flag=1;
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%s",num);
if(flag)
{
flag=isConsistent(&root,num);
}
}
if(flag)
printf("YES\n");
else
printf("NO\n");
myfree(&root);
}
return 0;
}
//判断某个字符串是否为另一个字符串的前缀。若是,则返回0,否则,返回1.
int isConsistent(Trie ** root,char num[])
{
int i,len=strlen(num),j;
//isOk只有在插入新节点的时候才会为1.如果一直都没有插入新节点,
//说明该字符串是前面某个字符串的前缀。比如,前面插入字符串"9110",
//现在的字符串为“911”,此时不需要新建节点,只需一步一步向下遍历既可。
//遍历完"911"之后,isOk仍然为0.
//如果在遍历的过程中遇到某个字符串的最后一个字符,比如“91”的'1',则
//会直接返回0,不会再继续插入"911"的最后一个'1'.
int isOk=0;
if(*root==NULL)
{
*root=(Trie *)malloc(sizeof(Trie));
for(i=0;i<MAX;i++)
(*root)->next[i]=NULL;
(*root)->v='0';
}
Trie *p=*root;
for(i=0;i<len;i++)
{
int id=num[i]-'0';
if(p->next[id]==NULL)
{
p->next[id]=(Trie *)malloc(sizeof(Trie));
p=p->next[id];
p->v='0';
isOk=1;
for(j=0;j<MAX;j++)
p->next[j]=NULL;
}
else
{
p=p->next[id];
if(p->v=='1')
return 0;//前面插入的字符串中存在该字符串的前缀,比如说,前面插入字符串"911",现在插入的字符串为“9110”。
}
}
p->v='1';
return isOk;
}
void myfree(Trie ** root)
{
Trie *p=*root;
int i;
for(i=0;i<MAX;i++)
{
if(p->next[i]!=NULL)
{
myfree(&(p->next[i]));
}
}
free(p);
p=NULL;
}