给定两个单词(开始词与结束词)及字典列表,找出可以从开始词到结束词之间最短的转换序列,转换规则:
- 转换系列中所有的单词等长
- 每次转换只允许改变一个字母
- 每次转换得到的词必须在字典列表中
- 开始词不一定在字典中
如给定如下的开始词,结束词及字典列表: beginWord = "hit" endWord = "cog" dictList = ["hot","dot","dog","lot","log","cog"] 我们可以找到两条最短的转换序列 hit->hot->dot->dog->cog hit->hot->lot->log->cog
输入
输入包含两行,第一行输入开始词及结束词,第二行输入字典列表。所有单词仅包含 a~z 的小写字母,单词最大长度为 5,字典最多包含 5000 个单词,开始词与结束词均不为空且不相等,同时字典中无重复单词。
输出
如存在最短的序列,请按行输出,多个序列按字典序升序排列;如未找到最短的序列,输出 "No Solution"
测试输入 | 期待的输出 | 时间限制 | 内存限制 | 额外进程 | |
---|---|---|---|---|---|
测试用例 1 | 以文本方式显示
| 以文本方式显示
| 1秒 | 1024KB | 0 |
题解:
这题挺难的,大部分的方法基本上是构造邻接矩阵或邻接表,而且网上基本上给的都是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的话还要重新开数组记录,输出就是递归输出就好。
下面给出杨珏成大佬和张浩大佬的源攻略:
杨珏成大佬的:
可用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不到的字节
比如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,为了接下来在防循环方面的操作故标记5001
short 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;
else
return 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;
}