Word Ladder II
Question
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
- Only one letter can be changed at a time
- Each intermediate word must exist in the dictionary
Example
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
Note
- All words have the same length.
- All words contain only lowercase alphabetic characters.
Thoughts
分析:本题主要的框架和上一题是一样,但是还要解决两个额外的问题:
- 怎样保证求得所有的最短路径
- 怎样构造这些路径
第一问题:
不能像上一题第二点注意那样,找到一个单词相邻的单词后就立马把它从字典里删除,因为当前层还有其他单词可能和该单词是相邻的,这也是一条最短路径,比如hot->hog->dog->dig和hot->dot->dog->dig,找到hog的相邻dog后不能立马删除,因为和hog同一层的单词dot的相邻也是dog,两者均是一条最短路径。但是为了避免进入死循环,再hog、dot这一层的单词便利完成后dog还是得从字典中删除。即等到当前层所有单词遍历完后,和他们相邻且在字典中的单词要从字典中删除。 如果像上面那样没有立马删除相邻单词,就有可能把同一个单词加入bfs队列中,这样就会有很多的重复计算(比如上面例子提到的dog就会被2次加入队列)。因此我们用一个哈希表来保证加入队列中的单词不会重复,哈希表在每一层遍历完清空(代码中hashtable)。 当某一层的某个单词转换可以得到end单词时,表示已经找到一条最短路径,那么该单词的其他转换就可以跳过。并且遍历完这一层以后就可以跳出循环,因为再往下遍历,肯定会超过最短路径长度
这里我用的是类似level order traversal 的方法
第二个问题:
为了输出最短路径,我们就要在比bfs的过程中保存好前驱节点,比如单词hog通过一次变换可以得到hot,那么hot的前驱节点就包含hog,每个单词的前驱节点有可能不止一个,那么每个单词就需要一个数组来保存前驱节点。为了快速查找因此我们使用哈希表来保存所有单词的前驱路径,哈希表的key是单词,value是单词数组.
有了上面的前驱路径,可以从目标单词开始递归的构造所有最短路径
python dict
Solution
我的code,正确性是对的,但是超时
# -*- coding: utf-8 -*-
from collections import deque
from sets import Set
import sys
class Solution:
# @param start, a string
# @param end, a string
# @param dict, a set of string
# @return a list of lists of string
def findLadders(self, start, end, dict):
if not start or not end or not dict:
return None
self.minLength = sys.maxint
self.resultList = []
prevHash = {}
#bfs
level = 1
queue = deque()
removeQueue = Set()
preEnd = Set()
queue.append([start, level])
curLevelNum = 1
nextLevelNum = 0
prevHash[start] = None
while queue:
c = queue.popleft()
curr = c[0]
curLev= c[1]
curLevelNum -= 1
removeQueue.add(curr)
if self.isNeighbour(end, curr):
preEnd.add(curr)
#return curLev + 1
#loop a - z to find neighbors
ns = self.getNeighbours(curr, dict)
for word in ns:
if curr != word:
queue.append([word, curLev + 1])
removeQueue.add(word)
nextLevelNum += 1
if word in prevHash:
if curr not in prevHash[word]:
prevHash[word].append(curr)
else:
prevHash[word] = [curr]
#remove already used word, cause we only need one shortest way here
if curLevelNum == 0:
while removeQueue:
c = removeQueue.pop()
if c in dict:
dict.remove(c)
curLevelNum = nextLevelNum
nextLevelNum = 0
for curr in preEnd:
pa = []
pa.append(end)
pa.append(curr)
pathes = self.getPathes(start, curr, prevHash, pa)
return self.resultList
def getNeighbours(self, curr, dict):
#26 characters(a-z)
neighbours = []
l = len(curr)
for i in range(l):
for c in 'abcdefghijklmnopqrstuvwxyz':
s = curr[:i] + c + curr[i+1:]
if s in dict:
neighbours.append(s)
return neighbours
def isNeighbour(self, curr, word):
len1 = len(curr)
len2 = len(word)
if len1 != len2:
return False
else:
diff = 0
for i in range(len1):
if curr[i] != word[i]:
diff += 1
if diff > 1:
return False
return True
def getPathes(self, start, curr, prevHash, curList):
if curr == start:
if len(curList) < self.minLength:
self.minLength = len(curList)
newList = list(curList)
newList.reverse()
self.resultList = [newList]
elif len(curList) == self.minLength:
newList = list(curList)
newList.reverse()
self.resultList.append(newList)
else:
parentList = prevHash[curr]
for parent in parentList:
curList.append(parent)
self.getPathes(start, parent, prevHash, curList)
curList.pop()
可以通过的code
# -*- coding: utf-8 -*-
from collections import deque
from sets import Set
class Solution:
# @param start, a string
# @param end, a string
# @param dict, a set of string
# @return a list of lists of string
def findLadders(self, start, end, dict):
def buildpath(path, word):
if len(prevMap[word])==0:
path.append(word); currPath=path[:]
currPath.reverse(); result.append(currPath)
path.pop();
return
path.append(word)
for iter in prevMap[word]:
buildpath(path, iter)
path.pop()
result=[]
prevMap={}
length=len(start)
for i in dict:
prevMap[i]=[]
candidates=[set(),set()]; current=0; previous=1
candidates[current].add(start)
while True:
current, previous=previous, current
for i in candidates[previous]: dict.remove(i)
candidates[current].clear()
for word in candidates[previous]:
for i in range(length):
part1=word[:i]; part2=word[i+1:]
for j in 'abcdefghijklmnopqrstuvwxyz':
if word[i]!=j:
nextword=part1+j+part2
if nextword in dict:
prevMap[nextword].append(word)
candidates[current].add(nextword)
if len(candidates[current])==0: return result
if end in candidates[current]: break
buildpath([], end)
return result