宽度优先遍历(bfs)(3)——最小路径问题
欢迎来到博主的专栏:算法解析
博主ID:代码小豪
文章目录
- 最小路径问题
- leetcode127——单词接龙
- 题目解析
- 算法原理
- 题解代码
最小路径问题
最小路径问题分为路权为1的最小路径问题和路权不为1的最小路径问题。我们这里只讨论路权为1的最小路径问题,因为这个问题最基础也是最简单的最小路径问题。
接下来就是解释什么是路权,我们可以简单的认为路权是从一个点到最近的一个点的距离。那么路权为1就可以简单的认为从一个点到达最近的一个点的距离为1。
那么接下来我们可以画出如下的一个道路。
我们以A为起点,箭头表示当前节点可以到达下一个点的方向,一个点到达最近一个点的距离为1,找到从A到达H的最短路径。
我们可以从起点开始进行一次宽度优先遍历,将A点能达到的所有下一个节点加入到队列中,以此类推,那么A点的宽度优先遍历如下:
因此从A到H的最短路径为6。相信大家也会有这么一个疑问,就是为什么宽度优先遍历的结果一定是最短的路径呢?其实这里也有一点贪心算法的原理,如果我们要证明起来就超过博主现在的能力了。因此博主这里拿一个例子演示一下吧,首先我们从a点到达d点的路径有三个。
我们可以发现,宽度优先遍历的结果(即距离为3)总是最优的,其他的路径的距离都为4,但是我们宽度优先遍历的距离则是3。
第二个常见场景则是迷宫问题了
迷宫问题的规则很简单,玩家从start位置开始,红色的区域是不能移动到的,所谓的走出迷宫,就是从start区域来到边界,现在求从start到走出区域的最小距离。
我们直接从start位置开始宽度优先遍历,当start来到边界时,直接返回宽度遍历层数,就是走出迷宫的最小路径。
如上图,走出迷宫的最小距离为2。
leetcode127——单词接龙
题目解析
我们以示例1为例,我们会得到一个起始单词,然后会有一个末尾单词,要求每次只能修改其实单词中的其中一个字母,而且修改后的单词必须在字符串序列"wordslist"当中。比如hit可以变成hot,hot在wordlist当中,然后hot可以将’h’改为’d’组成dot,其中dot也在wordlist当中,以此类推直到hit能够来到末尾单词cog为止。
但是在题目中cog未必在wordslist当中,或者是不存在从起始单词到达末尾单词的转换序列。此时我们返回0.
算法原理
有人看到这里就要问了,这算是哪门子的最小路径问题?哪来的路径?这全是字符串吗?实际上如果题目要求我们有一个推导的序列,以及关键词最小时,我们总是要优先想起这些算法:动态规划,贪心,bfs,dfs。
那么其实这个问题是有路径的,我们可以将hit->hot->dot->dog->cog。视作一个路径。那么问题来了,其他路径呢?
我们以起始单词ab为例,要求这个单词来到末尾单词zz。那么它的所有可能路径如下(实际上所有可能的路径是26^2,博主不可能全画出来):
那么我们从ab开始做一次宽度优先遍历,如下:
因此从ab转换成zz的最小次数为2.但是在题目中还存在可用字符串序列
wordslist的限制。因此我们要保证进行bfs的节点存在于wordslist当中。
那么此时解题思路就非常明确了,我们可以将beginword的每个字母修改成a~z的任意一个(即遍历所有结果),如果修改后的单词在wordslist当中,就将修改后的单词加入队列,接着从队列中拿出队头元素(修改后的单词)。接着将取出的队头元素的第任意一个单词替换成a~z的任意一个,若修改后的单词存在于wordslist中,我们将其加入队列。一次类推,直到第N次bfs找到endword为止,此时返回bfs的次数N。
要注意的一个细节是,如果wordslist中已经被bfs过的单词要标记下来,防止重复bfs导致死循环。比如我们从aob变成zzz。wordslist中存在[aoz,aoc,azz,zzz]。那么我们将aob的任意一个字母进行修改的合法结果就是[aoz
,aoc]
。如果对aoz进行宽度优先遍历,那么可能的结果是[azz,aoc
],如果aoc进行bfs的合法结果则是[azz,aoz
],可以发现如果不对已经bfs的单词进行标记的话,会出现重复对某个单词进行bfs,导致死循环。
因此我们可以用一个哈希表,将已经bfs过的单词标记一下。避免重复进入。
还有一个细节就是我们在bfs的时候,需要判断bfs的结果是否存在于wordslist中,因此我们需要频繁的在wordslist中进行查找操作,因此我们可以用哈希表或者RBTree来优化查找的时间复杂度。这里博主使用哈希表。
题解代码
class Solution {
public:int ladderLength(string beginWord, string endWord, vector<string>& wordList) {unordered_set<string> check(wordList.begin(),wordList.end());//检查bfs结果是否存在wordslist中unordered_set<string> used;//已经遍历过的单词if(beginWord==endWord) return 0;if(!check.count(endWord)) return 0;int step=0;//记录这是第几次遍历queue<string> q;q.push(beginWord);while(q.size()){int sz=q.size();step++;while(sz--){string& str=q.front();for(int i=0;i<str.size();i++){string tmp=str;//队头元素if(tmp==endWord) return step;//bfs到了endwordfor(char ch='a';ch<='z';ch++){tmp[i]=ch;//修改单词中任意一个字母替换为chif(check.count(tmp)&&!used.count(tmp)){//如果tmp存在于wordslist当中&&tmp没有被用过q.push(tmp);used.insert(tmp);}}}q.pop();}}return 0;//没有bfs到就是不存在}
};