127单词接龙(广度优先搜索、双向广度优先搜索)

1、题目描述

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

  • 每次转换只能改变一个字母。
  • 转换过程中的中间单词必须是字典中的单词。

说明:

  • 如果不存在这样的转换序列,返回 0。
  • 所有单词具有相同的长度。
  • 所有单词只由小写字母组成。
  • 字典中不存在重复的单词。
  • 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

2、示例

输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

输出: 5

解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
     返回它的长度 5。

3、题解

问题抽象成把所有单词看成结点,相差一个字符的两个单词就是相邻的节点,给定起始节点和终止节点,找最短路径。本质上就是在正常的遍历的基础上,去剪枝,从而提高速度,如果使用简单的递归DFS,当数据达到一定程度超时。

  • 剪枝思想(核心思想):在考虑第 k 层的某一个单词,如果这个单词在第 1 到 k-1 层已经出现过,我们其实就不用继续向下探索了,该序列路径一定不是最短转换序列。

解法一:BFS

基本思想:BFS

  • mindepth用于记录当前单词cur最先出现的层数,dict空间换时间用于查找cur相邻的单词,queue用于层次遍历
  • 先把该层的所有单词记录深度,这时mindepth记录的才是单词最先出现的层数
  • 遍历当前层所有单词,记录每个单词相邻的在之前层没有出现过所有单词入队列queue
  • 查找当前单词cur的下一个转换的单词:和cur只相差一个字母
  • 在考虑第 k 层的某一个单词,如果这个单词在第 1 到 k-1 层已经出现过,我们其实就不用继续向下探索了

解法二:双向BFS

基本思想:双向BFS,两个队列queue1、queue2分别从beginWord和endWord出发查找相邻的单词(这些单词在之前层没有出现过),直到两个队列中遇到相同的单词,至于每次从哪个方向扩展,我们可以每次选择需要扩展的节点数少的方向进行扩展。

#include<iostream>
#include<algorithm>
#include<vector>
#include<unordered_map>
#include<unordered_set>
#include<list>
using namespace std;
class Solution {
public:
	int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
		//基本思想:BFS
		unordered_map<string, int> mindepth;    //mindepth用于记录当前单词cur最先出现的层数
		unordered_set<string> dict(wordList.begin(), wordList.end()); //空间换时间,用于查找cur相邻的单词
		list<string> queue;    //queue用于层次遍历
		int flag = 0;    //标记是否已经遇到endWord
		queue.push_front(beginWord);
		int depth = 0;    //层次遍历的深度
		while (!queue.empty())
		{
			depth++;
			int len = queue.size();
			//先把该层的所有单词记录深度,这时mindepth记录的才是单词最先出现的层数
			for (auto i = queue.begin(); i != queue.end(); i++)
			{
				if (mindepth.find(*i) == mindepth.end())
					mindepth[*i] = depth;
			}
			//遍历当前层所有单词,记录每个单词相邻的在之前层没有出现过所有单词入队列queue
			for (int i = 0; i < len; i++)
			{
				string cur = queue.back();
				queue.pop_back();
				if (cur == endWord)
					flag = 1;
				string temp = cur;
				//查找当前单词cur的下一个转换的单词:和cur只相差一个字母
				for (int j = 0; j < cur.size(); j++)
				{
					for (char c = 'a'; c <= 'z'; c++)
					{
						if (cur[j] == c)
							continue;
						char oldc = cur[j];
						cur[j] = c;
						//在考虑第 k 层的某一个单词,如果这个单词在第 1 到 k-1 层已经出现过,我们其实就不用继续向下探索了
						if (dict.find(cur) != dict.end() && mindepth.find(cur) == mindepth.end())
						{
							queue.push_front(cur);
						}
						cur[j] = oldc;
					}
				}
			}
			if (flag == 1)
				break;
		}
		if (flag == 1)
			return depth;
		else
			return 0;
	}
};
class Solution1 {
public:
	int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
		//基本思想:双向BFS,两个队列queue1、queue2分别从beginWord和endWord出发查找相邻的单词(这些单词在之前层没有出现过),直到两个队列中遇到相同的单词
		//至于每次从哪个方向扩展,我们可以每次选择需要扩展的节点数少的方向进行扩展
		unordered_map<string, int> mindepth1;    //mindepth用于记录beginWord往下相邻单词最先出现的层数
		unordered_map<string, int> mindepth2;    //mindepth用于记录endWord往上相邻单词最先出现的层数
		unordered_set<string> dict(wordList.begin(), wordList.end()); //空间换时间,用于查找cur相邻的单词
		if (dict.find(endWord) == dict.end())
			return 0;
		list<string> queue1;    //queue1用于beginWord往下层次遍历
		list<string> queue2;    //queue2用于endWord往上层次遍历
		int flag = 0;    //标记两个队列是否遇到相同的单词
		queue1.push_front(beginWord);
		queue2.push_front(endWord);
		int depth1 = 0;    //beginWord往下层次遍历的深度
		int depth2 = 0;    //endWord往上层次遍历的深度
		while (!queue1.empty() && !queue2.empty())
		{
			int len1 = queue1.size();
			int len2 = queue2.size();
			int len;
			list<string> queue;
			int depth;
			unordered_map<string, int> mindepth;
			if (len1 <= len2)
			{
				depth1++;
				queue = queue1;
				mindepth = mindepth1;
				len = len1;
				depth = depth1;
			}
			else
			{
				depth2++;
				queue = queue2;
				mindepth = mindepth2;
				len = len2;
				depth = depth2;
			}
			//先把该层的所有单词记录深度,这时mindepth记录的才是单词最先出现的层数
			for (auto i = queue.begin(); i != queue.end(); i++)
			{
				if (mindepth.find(*i) == mindepth.end())
					mindepth[*i] = depth;
			}
			//遍历当前层所有单词,记录每个单词相邻的在之前层没有出现过所有单词入队列queue
			for (int i = 0; i < len; i++)
			{
				string cur = queue.back();
				queue.pop_back();
				string temp = cur;
				//查找当前单词cur的下一个转换的单词:和cur只相差一个字母
				for (int j = 0; j < cur.size(); j++)
				{
					for (char c = 'a'; c <= 'z'; c++)
					{
						if (cur[j] == c)
							continue;
						char oldc = cur[j];
						cur[j] = c;
						//在考虑第 k 层的某一个单词,如果这个单词在第 1 到 k-1 层已经出现过,我们其实就不用继续向下探索了
						if (dict.find(cur) != dict.end() && mindepth.find(cur) == mindepth.end())
						{
							queue.push_front(cur);
						}
						cur[j] = oldc;
					}
				}
			}
			if (len1 <= len2)
			{
				queue1 = queue;
				mindepth1 = mindepth;
			}
			else
			{
				queue2 = queue;
				mindepth2 = mindepth;
			}
			//直到两个队列中遇到相同的单词flag=1跳出循环
			for (auto i = queue1.begin(); i != queue1.end() && flag==0; i++)
			{
				for (auto j = queue2.begin(); j != queue2.end() && flag==0; j++)
				{
					if (*i == *j)
						flag = 1;
				}
			}
			if (flag == 1)
				break;
		}
		if (flag == 1)
			return depth1 + depth2 + 1;
		else
			return 0;
	}
};
int main()
{
	Solution1 solute;
	string beginWord = "hog";
	string endWord = "cog";
	vector<string> wordList = { "cog" };
	cout << solute.ladderLength(beginWord, endWord, wordList) << endl;
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值