23. 单词接龙

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

  1. 转换系列中所有的单词等长
  2. 每次转换只允许改变一个字母
  3. 每次转换得到的词必须在字典列表中
  4. 开始词不一定在字典中
如给定如下的开始词,结束词及字典列表:
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"

测试输入关于“测试输入”的帮助期待的输出关于“期待的输出”的帮助时间限制关于“时间限制”的帮助内存限制关于“内存限制”的帮助额外进程关于“{$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,为了接下来在防循环方面的操作故标记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;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值