23. 单词接龙

来源:互联网 发布:淘宝男士家居服 编辑:程序博客网 时间:2024/06/05 10:20

给定两个单词(开始词与结束词)及字典列表,找出可以从开始词到结束词之间最短的转换序列,转换规则:

  1. 转换系列中所有的单词等长
  2. 每次转换只允许改变一个字母
  3. 每次转换得到的词必须在字典列表中
  4. 开始词不一定在字典中
如给定如下的开始词,结束词及字典列表:beginWord = "hit"endWord = "cog"dictList = ["hot","dot","dog","lot","log","cog"]我们可以找到两条最短的转换序列hit->hot->dot->dog->coghit->hot->lot->log->cog

输入

输入包含两行,第一行输入开始词及结束词,第二行输入字典列表。所有单词仅包含 a~z 的小写字母,单词最大长度为 5,字典最多包含 5000 个单词,开始词与结束词均不为空且不相等,同时字典中无重复单词。

输出

如存在最短的序列,请按行输出,多个序列按字典序升序排列;如未找到最短的序列,输出 "No Solution"

测试输入关于“测试输入”的帮助期待的输出关于“期待的输出”的帮助时间限制关于“时间限制”的帮助内存限制关于“内存限制”的帮助额外进程关于“{$a} 个额外进程”的帮助测试用例 1以文本方式显示

  1. hit cog↵
  2. hot dot dog lot log cog↵
以文本方式显示
  1. hit->hot->dot->dog->cog↵
  2. hit->hot->lot->log->cog↵
1秒1024KB0

题解:


这题挺难的,大部分的方法基本上是构造邻接矩阵或邻接表,而且网上基本上给的都是C++或java等语言实现的,因为有比较好的函数库支持一些写法的实现,用纯C来实现就比较麻烦。讨论区里杨珏成大佬给了一个比较有新意的方法(我表示真的真的服了),使用五维数组加BFS,然后使用回溯表输出结果。接下来我将结合我个人实际操作进行讲解。

先构建一个字典Instr_char[5001][6]={'\0'},这个字典用来记录输入的单词(第一行的首单词可以用字典第一行InStr_char[0]记录,尾单词用另一个char型数组EdStr记录)。然后是构造五维数组StrMap[27][27][27][27][27],这个是判断单词是否存在的,我是建的结构体数组,因为我当时想为了后续的回溯表,所以还用一个值index记录这个单词在字典中的位置,本身值num赋为5001,因为单词数最多为5000。

搜索部分:

对于每个队首结点
进入一个 j:0->25的for循环,
每一次的循环中分别将队首字符串的第0-4位替换为j,查找五维地图上新字符串是否存在
即分别查找StrMap[i][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num是否存在
若存则进入第二步判断:
如果当前步数小于StrMap.num上储存的值,则入队当前字符,并将num该位赋为当前步数
否则不进入操作

为什么要这样?

因为到达同一个中间词时,
也有可能有多条同样步长的变换序列,
这些序列均需入队,这意味着中间节点可能多次入队,
不能直接用vis数组标1,而要考虑当前步长是否仍是最短路

此外要注意字典序!!字典序!!字典序!!
先说上述方法的问题:
对于字符串aaaaa,可能先入队aabaa,后入队aaaac,然而明显后者字典序更靠前

对于以字典序入队部分:(这个字典序入队操作的方法是参考张浩大佬的思路)

做十个循环(Ctr C...)
比如hit
先循环第一个字母h
a-h,
再入第二个字母
a-i,
接着第三个字母
a-t;
然后还是第三个字母
h-z;
再是第二个字母
i-z;
最后第一个字母
h-z;
这样就能保证字典序。(可以手动模拟一下,杨珏成大佬是先入队后用排序的,个人感觉比较麻烦)

回溯部分:

就是子节点记录父节点,这个记录可以放在队列里,因为是纯C模拟队列,所以开的数组不会清空,不然像使用C++的STL的话还要重新开数组记录,输出就是递归输出就好。


下面给出杨珏成大佬和张浩大佬的源攻略:

杨珏成大佬的:

思路简介:由于字符长度不超过5,每位字符有26种可能性
可用int?map[26][26][26][26][26]记录字符串"\a1\a2\a3\a4\a5"在字典中的存在性
?
例如输入str[5]="abc",首先获取字符串长度n,然后对前n位-='a',之后用五维数组标记,即map[str[0]][str[1]][str[2]][str[3]][str[4]]=5001;?
//5001即为MAXN+1,后面会讲为什么
实测不会爆内存
?
回溯法部分:
单开一个parent数组记录父结点,用id1记录每个入队结点的绝对序数
用id2记录当前头节点的id1,每次取出队首结点时令id2=q.head.id1,
即从id1=0开始,每个新节点入队时均将id1++,同时将parent[id1]=id2
再开个list数组,然后strcpy(list[id2],q.head.str),用来记录当前头节点对应的字符串;
若搜到终止词,不要直接退出,
用一个mark数组记录每次搜索到终点时终止节点对应的parent下标id1,即mark[flag]=id1,flag++;
flag如果等于0则表示没有搜到通路
直到步长大于最短步长或队列为空才退出
退出后从mark里依次读取序列终止节点对应的id,对于每个id回溯整条parent序列,并顺序输出list中对应的字符串
?
?
搜索部分:
?
对于每个队首结点
进入一个 j:0->25的?for循环,
?
每一次的循环中分别将队首字符串的第0-4位替换为j,查找五维地图上新字符串是否存在
即分别查找map[j][now[1]][now[2]][now[3]][now[4]],map[now[0]][j][now[2]][now[3]][now[4]] ......map[now[0]][now[1]][now[2]][now[3]][j]是否存在
?
若存则进入第二步判断:
?如果当前步数小于map上储存的值,则入队当前字符,并将map该位赋为当前步数
否则不进入操作
?
为什么要这样?
?
因为到达同一个中间词时,
也有可能有多条同样步长的变换序列,
这些序列均需入队,这意味着中间节点可能多次入队,
不能直接用vis数组标1,而要考虑当前步长是否仍是最短路
?
?
?
此外要注意字典序!!字典序!!字典序!!
先说上述方法的问题:
对于字符串aaaaa,可能先入队aabaa,后入队aaaac,然而明显后者字典序更靠前
?
所以每次取出头节点时,先记录队尾节点对应的数组序号,是队尾!不是队首
然后开一个int size=0;
在头节点没改变时,每次有新单词入队则让size++
最后qsort(q+rear,size,sizeof(node),cmp);
?
意思很明显,就是对当前头节点对应的所有新节点按照字典序排列
?
通过保证每个当前节点入队的单词均按照字典序入队,就可以保证所有序列字典序排序,因为此时整棵回溯树按照字典序排列子树
按照以上思路即可AC
还要注意所有记录节点信息的数组最好都开大点,我开了10万
?
总结:
此思路的空间复杂度,在字符长度较短时低于邻接矩阵的思路,相比5000*5000个int的空间占用,五维数组只需要12000000不到的字节

张浩大佬的:

1.关于字典序,我觉得排序比较麻烦,所以在入队的时候就保证字典序;方法也很简单,做十个循环(Ctrl c.....)代码量比较大啊
比如hit
先循环第一个字母h
a-h,
再入第二个字母
a-i,
接着第三个字母
a-t;
?
然后还是第三个字母
h-z;
再是第二个字母
i-z;
最后第一个字母
h-z;
这样就能保证字典序。
然后回溯法部分,跟杨珏成思路一样,每一个结构体队列元素用一个id变量储存父亲节点的队列数组的下标。
每一次找到target之后,照着id回溯就好,父到子是一对多,但每个子节点只有一个父亲节点。
然后我是开了vis数组的,开始输入list的时候,map[][][][][]=1;vis[][][][][]=5001;
map用来检查是否单词是否存在,vis检查是不是最小步数到达该节点。
做题时踩了一个坑,因为存在多条最优路径,所以找到target不直接结束,而是接着找。所以找到了不是最优的路径,用一个变量记步数就好。
两组简单样例
输入:
hit cof
hot dot dog lot log cog cof
输出:
hit->hot->dot->dog->cog->cof
hit->hot->lot->log->cog->cof
输入
hit log
hot dot dog lot log cog
输出:
hit->hot->lot->log

这个代码出现玄学错误了,之前一直没事,提交后也顺利AC,结果暑假有次有同学找我要代码发现这个代码无法输出结果,我把AC的提交历史记录调出来重新运行也是无法输出。。。尴尬。。。有大佬知道这是什么情况吗?若有大佬知道发现bug点,麻烦告知一下,感激不尽!

#include<stdio.h>//五维数组加BFS的方法#include<stdlib.h>#include<string.h>int LenStr;//串长 short int InStr_int[5006][5];//承担将char型变成int型的记录 char EdStr[6] = { '\0' };//记录终点 int head = 0, end = 0;//队列移动的指针 struct node1{short int num;//第一次标记5001,表示这个单词是存在的,之所以用5001标记,是因为最最最大步数不超过5000,为了接下来在防循环方面的操作故标记5001short int index;//记录当前单词在字典中的位置};struct node1 StrMap[27][27][27][27][27];//构建五维数组,主要用来判断这个单词是否存在char InStr_char[5001][6] = { '\0' };//构建字典struct node3{short int index;//记录本元素的串在字典中的位置 short int step;//步数 int FatherIndex;//记录父亲队列元素在队列中的位置,方便后来回溯输出int queue_index;//记录本元素在队列中的位置 };struct node3 Queue[100005];int Q_empty()//队列是否为空 {if (head == end)return 1;elsereturn 0;}void Q_in(struct node3 InI)//入队 {Queue[end] = InI;Queue[end].queue_index = end;end++;}void Print(int Fatherindex)//回溯输出{if (Fatherindex == 0){printf("%s", InStr_char[Queue[Fatherindex].index]);return;}Print(Queue[Fatherindex].FatherIndex);printf("->%s", InStr_char[Queue[Fatherindex].index]);}int main(){int StrNum = 1, lens;for (int i = 0; i < 5001;)//不知道为啥用memset给InStr_int赋值26会出现错误,故只能用循环了{InStr_int[i][0] = 26; InStr_int[i][1] = 26; InStr_int[i][2] = 26; InStr_int[i][3] = 26; InStr_int[i][4] = 26;InStr_int[i + 1][0] = 26; InStr_int[i + 1][1] = 26; InStr_int[i + 1][2] = 26; InStr_int[i + 1][3] = 26; InStr_int[i + 1][4] = 26;InStr_int[i + 2][0] = 26; InStr_int[i + 2][1] = 26; InStr_int[i + 2][2] = 26; InStr_int[i + 2][3] = 26; InStr_int[i + 2][4] = 26;InStr_int[i + 3][0] = 26; InStr_int[i + 3][1] = 26; InStr_int[i + 3][2] = 26; InStr_int[i + 3][3] = 26; InStr_int[i + 3][4] = 26;InStr_int[i + 4][0] = 26; InStr_int[i + 4][1] = 26; InStr_int[i + 4][2] = 26; InStr_int[i + 4][3] = 26; InStr_int[i + 4][4] = 26;i += 5;}memset(StrMap, 0, sizeof(StrMap));scanf("%s %s", InStr_char[0], EdStr);LenStr = strlen(InStr_char[0]);for (int i = 0; i < LenStr; i++)InStr_int[0][i] = InStr_char[0][i] - 'a';StrMap[InStr_int[0][0]][InStr_int[0][1]][InStr_int[0][2]][InStr_int[0][3]][InStr_int[0][4]].num = 5001;StrMap[InStr_int[0][0]][InStr_int[0][1]][InStr_int[0][2]][InStr_int[0][3]][InStr_int[0][4]].index = 0;while (~scanf("%s", InStr_char[StrNum]))//读入字典{lens = strlen(InStr_char[StrNum]);if (strcmp(InStr_char[0], InStr_char[StrNum]) == 0){memset(InStr_char[StrNum], '\0', sizeof(InStr_char[StrNum]));continue;}for (int i = 0; InStr_char[StrNum][i] != '\0'; i++)InStr_int[StrNum][i] = InStr_char[StrNum][i] - 'a';if (lens == LenStr)StrMap[InStr_int[StrNum][0]][InStr_int[StrNum][1]][InStr_int[StrNum][2]][InStr_int[StrNum][3]][InStr_int[StrNum][4]].num = 5001;StrMap[InStr_int[StrNum][0]][InStr_int[StrNum][1]][InStr_int[StrNum][2]][InStr_int[StrNum][3]][InStr_int[StrNum][4]].index = StrNum;StrNum++;}struct node3 start, now, next;//start初始入队元素,now当前取队元素,next准备接下来入队元素start.index = 0; start.step = 1; start.FatherIndex = 0;Q_in(start);int flag = 5002;//用来标记最短路径数while (!Q_empty()){now = Queue[head];head++;if (now.step > flag)//如果步数超过最短路数,要退出break;if (strcmp(InStr_char[now.index], EdStr) == 0)//到达终点{Print(now.FatherIndex);printf("->%s\n", InStr_char[now.index]);flag = now.step;continue;}//接下来是直接以字典序入队,也可以先入队后字典序排序 ,不过会有重复入队情况 if (LenStr >= 1){for (int i = 0; i < InStr_int[now.index][0]; i++){if (StrMap[i][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num >= now.step){StrMap[i][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num = now.step;next.step = now.step + 1;next.index = StrMap[i][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 2){for (int i = 0; i < InStr_int[now.index][1]; i++){if (StrMap[InStr_int[now.index][0]][i][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num >= now.step){StrMap[InStr_int[now.index][0]][i][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num = now.step;next.step = now.step + 1;next.index = StrMap[InStr_int[now.index][0]][i][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 3){for (int i = 0; i < InStr_int[now.index][2]; i++){if (StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][i][InStr_int[now.index][3]][InStr_int[now.index][4]].num >= now.step){StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][i][InStr_int[now.index][3]][InStr_int[now.index][4]].num = now.step;next.step = now.step + 1;next.index = StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][i][InStr_int[now.index][3]][InStr_int[now.index][4]].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 4){for (int i = 0; i < InStr_int[now.index][3]; i++){if (StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][i][InStr_int[now.index][4]].num >= now.step){StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][i][InStr_int[now.index][4]].num = now.step;next.step = now.step + 1;next.index = StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][i][InStr_int[now.index][4]].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 5){for (int i = 0; i < InStr_int[now.index][4]; i++){if (StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][i].num >= now.step){StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][i].num = now.step;next.step = now.step + 1;next.index = StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][i].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 5){for (int i = InStr_int[now.index][4] + 1; i < 26; i++){if (StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][i].num >= now.step){StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][i].num = now.step;next.step = now.step + 1;next.index = StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][i].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 4){for (int i = InStr_int[now.index][3] + 1; i < 26; i++){if (StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][i][InStr_int[now.index][4]].num >= now.step){StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][i][InStr_int[now.index][4]].num = now.step;next.step = now.step + 1;next.index = StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][InStr_int[now.index][2]][i][InStr_int[now.index][4]].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 3){for (int i = InStr_int[now.index][2] + 1; i < 26; i++){if (StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][i][InStr_int[now.index][3]][InStr_int[now.index][4]].num >= now.step){StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][i][InStr_int[now.index][3]][InStr_int[now.index][4]].num = now.step;next.step = now.step + 1;next.index = StrMap[InStr_int[now.index][0]][InStr_int[now.index][1]][i][InStr_int[now.index][3]][InStr_int[now.index][4]].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 2){for (int i = InStr_int[now.index][1] + 1; i < 26; i++){if (StrMap[InStr_int[now.index][0]][i][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num >= now.step){StrMap[InStr_int[now.index][0]][i][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num = now.step;next.step = now.step + 1;next.index = StrMap[InStr_int[now.index][0]][i][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].index;next.FatherIndex = now.queue_index;Q_in(next);}}}if (LenStr >= 1){for (int i = InStr_int[now.index][0] + 1; i <26; i++){if (StrMap[i][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num >= now.step){StrMap[i][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].num = now.step;next.step = now.step + 1;next.index = StrMap[i][InStr_int[now.index][1]][InStr_int[now.index][2]][InStr_int[now.index][3]][InStr_int[now.index][4]].index;next.FatherIndex = now.queue_index;Q_in(next);}}}}if (flag == 5002)//未找到最短的序列printf("No Solution\n");return 0;}





原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 微信被盗密码被改绑定手机号怎么办 微信qq号登陆改密码忘记了怎么办 本人微信红包赌博输了50万怎么办 4g飞享套餐话费用完了怎么办 手机丢了查话费欠了几百块怎么办 注销电信手机卡里面的余额怎么办 联通手机卡注销后里面的余额怎么办 手机卡网上销户以后剩余话费怎么办 联通新号注册微信发不了短信怎么办 韩博士装机卡在驱动恢复怎么办 xp打印后程序服务没有运行怎么办 刚注册的微信显示异常怎么办 不小心删了照片怎么办不要钱 qq邀请好友辅助验证成功后怎么办 微信申诉怎么让好友发验证码怎么办 微信申诉好友都删除了怎么办 恋与制作人原来的帐号不见了怎么办 手机号被别人注册了手机银行怎么办 想上老婆的陌陌号但要验证码怎么办 中国家医居民端注册信息有误怎么办 别人给我充的q币怎么办 qq忘记密码手机号码也换了怎么办 手机号码不用了微信忘记密码怎么办 手机号码注销了微信忘记密码怎么办 微信忘记密码手机号码也换了怎么办 微信钱包里的钱莫名少了怎么办 被骗给人冲q币了怎么办 支付宝账户异常无法领取红包怎么办 微信q币支付错了怎么办 王者荣耀不小心把点卷用了怎么办 微信绑定的qq号密码忘记了怎么办 微信红包实名认证没银行卡怎么办 手机烂了换新手机微信支付宝怎么办 支付宝转账给别人号码没用了怎么办 微信转账验证码收不到怎么办 陌生网友生日叫我发红包怎么办 微信群的群主不小心推出群该怎么办 支付宝验证码被别人知道了怎么办 微信解除银行卡绑定零钱清零怎么办 qq号被盗了理财通的钱怎么办 工行转错账号的钱被冻结怎么办