126. 单词接龙 II(困难)

思路:

最短路径问题,很自然地想到要用广度优先遍历

 

代码:

class Solution {
    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
		//先将wordList放到哈希表中
		Set<String> wordSet=new HashSet<>(wordList);
		List<List<String>> res=new ArrayList<>();
		if(wordSet.size()==0||!wordSet.contains(endWord)){
			return res;
		}
		
		//第1步,使用广度优先,得到后继节点列表successors
		Map<String,Set<String>> successors=new HashMap<>();
		boolean found=bfs(beginWord,endWord,wordSet,successors);
		//如果没找到,就返回空的res
		if(!found){
			return res;
		}
		
		//第2步,使用回溯算法得到所有的最短路径列表,要基于后继节点successors
		Deque<String> path=new ArrayDeque<>();
		path.addLast(beginWord);
		dfs(beginWord,endWord,successors,path,res);
		return res;
		
    }
	
	private boolean bfs(String beginWord,String endWord,Set<String> wordSet,Map<String,Set<String>>successors){
		//使用一个队列,先进先出
		Queue<String> queue=new LinkedList<>();
		//初始化队列,把第一个单词加入队列
		queue.offer(beginWord);
		
		//利用一个Set哈希来存储是否访问过visited
		Set<String> visited=new HashSet<>();
		//visited妙,防止多次加入
		visited.add(beginWord);
		
		boolean found=false;
		
		int wordLen=beginWord.length();
		
		//将当前单词访问过的节点添加到nextLevelVisited
		Set<String> nextLevelVisited=new HashSet<>();
		
		while(!queue.isEmpty()){
			//是为了记录,下面queue的size会改变
			int currentSize=queue.size();
			//每一个单词
			for(int i=0;i<currentSize;i++){
				//是poll,则每一次都不会重复遍历之前的
				String currentWord=queue.poll();
				char[] charArray=currentWord.toCharArray();
				//每一个字母
				for(int j=0;j<wordLen;j++){
					//记录下当前的字母
					char originChar=charArray[j];
					//新奇的遍历字母的方式,仍然是k++
					//把当前字母的25种形式都换一遍
					for(char k='a';k<='z';k++){
						if(charArray[j]==k){
							continue;
						}
						//更改
						charArray[j]=k;
						String nextWord=new String(charArray);
						//单词列表里要包含该更改的字母
						//下面是一次循环
						if(wordSet.contains(nextWord)){
							//要没有访问过
							//visited妙
							if(!visited.contains(nextWord)){
								//如果刚好此单词与最后一个单词一致,则找到了一个
								if(nextWord.equals(endWord)){
									found=true;
								}
								//不管是不是最后一个单词,都算查到了
								//加入到该单词的查找列表中
								nextLevelVisited.add(nextWord);
								//加入到队列中再进行循环
								queue.offer(nextWord);
								
								//更新successor
								//下面是java 1.8之后支持的形式
								//意思是如果存在当前单词的哈希表,则直接用之前的,否则新建
								successors.computeIfAbsent(currentWord,a->new HashSet<>());
								//将每一个符合的单词加入,其实相当于nextLevelVisited
								successors.get(currentWord).add(nextWord);
							}
						}
						
					}
					//将更改的字母变回原来的那个字母,还原原来的单词
					charArray[j]=originChar;
				}
			}
			//一个单词的一个字符遍历结束,开始下一个字符
			if(found){
				break;
			}
			//visited妙
			visited.addAll(nextLevelVisited);
			//HashSet-->clear()方法
			nextLevelVisited.clear();
		}
		return found;
	}
	
	private void dfs(
					String beginWord
					,String endWord
					,Map<String,Set<String>> successors
					,Deque<String> path
					,List<List<String>> res){
		//回溯终止条件
		if(beginWord.equals(endWord)){
			res.add(new ArrayList<>(path));
			return;
		}
		
		if(!successors.containsKey(beginWord)){
			return;
		}
		
		//得到该单词的同一层单词
		Set<String> successorWords=successors.get(beginWord);
		for(String nextWord:successorWords){
			path.addLast(nextWord);
			dfs(nextWord,endWord,successors,path,res);
			//最后得到了endWord或没得到都把path弹出去,查找别的情况
			path.removeLast();
		}
	}
}

 

分解:

1)第一步:使用广度优先遍历(bfs)求得每个字符串的邻接表。并且bfs方法里还要设置一个flag,判断是否找到一个结果,若没有找到一个,则不需要再往下进行

2)找到每个单词的邻接表后,利用回溯法(dfs),求得符合条件的多条最短路径

3)广度优先遍历利用的是队列这个数据结构,bfs里要设置:

    i)同层字符串nextLevel

    ii)记录是否访问过,利用HashSet形式(visited)

    iii)设置flag判断是否找到至少一组以上(found)

    iv)核心:

           1.当队列不为空时,就不断poll出字符串

           2.记录每次弹出时当时队列中的字符串个数

           3.分别改变每个字符串的每个字母,将它替换为其他25个字母。遍历过一遍后将更改过的字母还原为原来的,然后继续超前遍历该字符串的别的字母

           4.多个if判断新字符串:i)单词列表中有 ii)没被访问过(visited里没有) iii#)刚好此单词为最后一个单词endWord,则设置flag为true,找到一个

           5.更新邻接表:

             下面是java 1.8之后支持的形式
             意思是如果存在当前单词的哈希表,则直接用之前的,否则新建

successor.computeIfAbsent(currentWord,a->new HashSet<>());
successor.get(currentWord).add(nextWord);

          6.在每遍历完一个字符串后,要将nextLevel存入visited并清空nextLevel

 

4)回溯法(dfs):

回溯终止条件

if(beginWord.equals(endWord)){
	res.add(new ArrayList<>(path));
	return;
}
		
if(!successors.containsKey(beginWord)){
	return;
}

核心代码:

//得到该单词的同一层单词
Set<String> successorWords=successors.get(beginWord);
for(String nextWord:successorWords){
	path.addLast(nextWord);
	dfs(nextWord,endWord,successors,path,res);
	//最后得到了endWord或没得到都把path弹出去,查找别的情况
    path.removeLast();
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值