Time complexity of this algorithm: Word Ladder
up vote
8
down vote
favorite
Question:
Given two words (beginWord and endWord), and a dictionary's word list,
find all shortest transformation sequence(s) from beginWord to
endWord, such that:
Only one letter can be changed at a time. Each transformed word must
exist in the word list. Note that beginWord is not a transformed word.
Example 1:
Input: beginWord = "hit",
endWord = "cog",
wordList =
["hot","dot","dog","lot","log","cog"]
Output: [ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
My solution is based on this idea, but how do I analyze the time and space complexity of this solution?
1) Perform a BFS starting at beginWord by transforming every letter to one of 26 letters, and see if the transformed word is in the wordList, if so, put in queue.
2) During BFS, maintain a graph of {word:nextWord} for all valid next
words
3) When a nextWord reaches endWord, do a backtracking DFS (pre-order
traversal) on the graph to get all paths.
class Solution:
def findLadders(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
wordListSet = set(wordList+[beginWord])
graph = collections.defaultdict(list)
q = set([beginWord])
count = 0
result =
while q:
count +=1
newQ = set()
for word in q:
wordListSet.remove(word)
for word in q:
if word == endWord:
self.getAllPaths(graph, beginWord, endWord, result, )
return result
for i in range(len(word)):
for sub in 'abcdefghijklmnopqrstuvwxyz':
if sub != word[i]:
newWord = word[:i] + sub + word[i+1:]
if newWord in wordListSet:
graph[word].append(newWord)
newQ.add(newWord)
q = newQ
return
def getAllPaths(self, graph, node, target, result, output):
#This is just a backtracking pre-order traversal DFS on a DAG.
output.append(node)
if node==target:
result.append(output[:])
else:
for child in graph[node]:
self.getAllPaths(graph,child, target, result, output)
output.pop()
I have a hard time coming up with the time and space complexity of it.
My contention:
Time: O(26*L*N + N
), where L
is average length of each word, and N
is the number of words in the wordList. Worst case here is every word transformed happens to be in the list, so each transformation needs 26 * length of word
. The DFS part is just O(N)
. So asymptotically it's just O(L*N)
Space: O(N)
python time-complexity breadth-first-search
add a comment |
up vote
8
down vote
favorite
Question:
Given two words (beginWord and endWord), and a dictionary's word list,
find all shortest transformation sequence(s) from beginWord to
endWord, such that:
Only one letter can be changed at a time. Each transformed word must
exist in the word list. Note that beginWord is not a transformed word.
Example 1:
Input: beginWord = "hit",
endWord = "cog",
wordList =
["hot","dot","dog","lot","log","cog"]
Output: [ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
My solution is based on this idea, but how do I analyze the time and space complexity of this solution?
1) Perform a BFS starting at beginWord by transforming every letter to one of 26 letters, and see if the transformed word is in the wordList, if so, put in queue.
2) During BFS, maintain a graph of {word:nextWord} for all valid next
words
3) When a nextWord reaches endWord, do a backtracking DFS (pre-order
traversal) on the graph to get all paths.
class Solution:
def findLadders(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
wordListSet = set(wordList+[beginWord])
graph = collections.defaultdict(list)
q = set([beginWord])
count = 0
result =
while q:
count +=1
newQ = set()
for word in q:
wordListSet.remove(word)
for word in q:
if word == endWord:
self.getAllPaths(graph, beginWord, endWord, result, )
return result
for i in range(len(word)):
for sub in 'abcdefghijklmnopqrstuvwxyz':
if sub != word[i]:
newWord = word[:i] + sub + word[i+1:]
if newWord in wordListSet:
graph[word].append(newWord)
newQ.add(newWord)
q = newQ
return
def getAllPaths(self, graph, node, target, result, output):
#This is just a backtracking pre-order traversal DFS on a DAG.
output.append(node)
if node==target:
result.append(output[:])
else:
for child in graph[node]:
self.getAllPaths(graph,child, target, result, output)
output.pop()
I have a hard time coming up with the time and space complexity of it.
My contention:
Time: O(26*L*N + N
), where L
is average length of each word, and N
is the number of words in the wordList. Worst case here is every word transformed happens to be in the list, so each transformation needs 26 * length of word
. The DFS part is just O(N)
. So asymptotically it's just O(L*N)
Space: O(N)
python time-complexity breadth-first-search
What part of your contention are you unsure about, and why?
– Scott Hunter
Nov 3 at 2:09
The time complexity i'm unsure, it seems need to be bigger than linear but I can't convince myself of so.
– user1008636
Nov 3 at 2:12
Graph search is linear in the size of the graph when the (potential) out-degree is fixed, as here.
– Davis Herring
Nov 3 at 17:55
Is my observation correct, that the dictionary property (sorted alphabetically) doesn't help in the average case because the change in leading characters makes the order worthless?
– Vroomfondel
Nov 7 at 9:01
This is not the question, but have you considered using the levenshtein distance? It would compute it in O(N * L**2) (Accroding to your notations) which is assymptotically worse than your solution. But better in many cases (Since L < 26) .
– BlueSheepToken
Nov 8 at 14:21
add a comment |
up vote
8
down vote
favorite
up vote
8
down vote
favorite
Question:
Given two words (beginWord and endWord), and a dictionary's word list,
find all shortest transformation sequence(s) from beginWord to
endWord, such that:
Only one letter can be changed at a time. Each transformed word must
exist in the word list. Note that beginWord is not a transformed word.
Example 1:
Input: beginWord = "hit",
endWord = "cog",
wordList =
["hot","dot","dog","lot","log","cog"]
Output: [ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
My solution is based on this idea, but how do I analyze the time and space complexity of this solution?
1) Perform a BFS starting at beginWord by transforming every letter to one of 26 letters, and see if the transformed word is in the wordList, if so, put in queue.
2) During BFS, maintain a graph of {word:nextWord} for all valid next
words
3) When a nextWord reaches endWord, do a backtracking DFS (pre-order
traversal) on the graph to get all paths.
class Solution:
def findLadders(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
wordListSet = set(wordList+[beginWord])
graph = collections.defaultdict(list)
q = set([beginWord])
count = 0
result =
while q:
count +=1
newQ = set()
for word in q:
wordListSet.remove(word)
for word in q:
if word == endWord:
self.getAllPaths(graph, beginWord, endWord, result, )
return result
for i in range(len(word)):
for sub in 'abcdefghijklmnopqrstuvwxyz':
if sub != word[i]:
newWord = word[:i] + sub + word[i+1:]
if newWord in wordListSet:
graph[word].append(newWord)
newQ.add(newWord)
q = newQ
return
def getAllPaths(self, graph, node, target, result, output):
#This is just a backtracking pre-order traversal DFS on a DAG.
output.append(node)
if node==target:
result.append(output[:])
else:
for child in graph[node]:
self.getAllPaths(graph,child, target, result, output)
output.pop()
I have a hard time coming up with the time and space complexity of it.
My contention:
Time: O(26*L*N + N
), where L
is average length of each word, and N
is the number of words in the wordList. Worst case here is every word transformed happens to be in the list, so each transformation needs 26 * length of word
. The DFS part is just O(N)
. So asymptotically it's just O(L*N)
Space: O(N)
python time-complexity breadth-first-search
Question:
Given two words (beginWord and endWord), and a dictionary's word list,
find all shortest transformation sequence(s) from beginWord to
endWord, such that:
Only one letter can be changed at a time. Each transformed word must
exist in the word list. Note that beginWord is not a transformed word.
Example 1:
Input: beginWord = "hit",
endWord = "cog",
wordList =
["hot","dot","dog","lot","log","cog"]
Output: [ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
My solution is based on this idea, but how do I analyze the time and space complexity of this solution?
1) Perform a BFS starting at beginWord by transforming every letter to one of 26 letters, and see if the transformed word is in the wordList, if so, put in queue.
2) During BFS, maintain a graph of {word:nextWord} for all valid next
words
3) When a nextWord reaches endWord, do a backtracking DFS (pre-order
traversal) on the graph to get all paths.
class Solution:
def findLadders(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
wordListSet = set(wordList+[beginWord])
graph = collections.defaultdict(list)
q = set([beginWord])
count = 0
result =
while q:
count +=1
newQ = set()
for word in q:
wordListSet.remove(word)
for word in q:
if word == endWord:
self.getAllPaths(graph, beginWord, endWord, result, )
return result
for i in range(len(word)):
for sub in 'abcdefghijklmnopqrstuvwxyz':
if sub != word[i]:
newWord = word[:i] + sub + word[i+1:]
if newWord in wordListSet:
graph[word].append(newWord)
newQ.add(newWord)
q = newQ
return
def getAllPaths(self, graph, node, target, result, output):
#This is just a backtracking pre-order traversal DFS on a DAG.
output.append(node)
if node==target:
result.append(output[:])
else:
for child in graph[node]:
self.getAllPaths(graph,child, target, result, output)
output.pop()
I have a hard time coming up with the time and space complexity of it.
My contention:
Time: O(26*L*N + N
), where L
is average length of each word, and N
is the number of words in the wordList. Worst case here is every word transformed happens to be in the list, so each transformation needs 26 * length of word
. The DFS part is just O(N)
. So asymptotically it's just O(L*N)
Space: O(N)
python time-complexity breadth-first-search
python time-complexity breadth-first-search
edited Nov 8 at 14:10
vencaslac
1,000217
1,000217
asked Oct 31 at 2:18
user1008636
61831120
61831120
What part of your contention are you unsure about, and why?
– Scott Hunter
Nov 3 at 2:09
The time complexity i'm unsure, it seems need to be bigger than linear but I can't convince myself of so.
– user1008636
Nov 3 at 2:12
Graph search is linear in the size of the graph when the (potential) out-degree is fixed, as here.
– Davis Herring
Nov 3 at 17:55
Is my observation correct, that the dictionary property (sorted alphabetically) doesn't help in the average case because the change in leading characters makes the order worthless?
– Vroomfondel
Nov 7 at 9:01
This is not the question, but have you considered using the levenshtein distance? It would compute it in O(N * L**2) (Accroding to your notations) which is assymptotically worse than your solution. But better in many cases (Since L < 26) .
– BlueSheepToken
Nov 8 at 14:21
add a comment |
What part of your contention are you unsure about, and why?
– Scott Hunter
Nov 3 at 2:09
The time complexity i'm unsure, it seems need to be bigger than linear but I can't convince myself of so.
– user1008636
Nov 3 at 2:12
Graph search is linear in the size of the graph when the (potential) out-degree is fixed, as here.
– Davis Herring
Nov 3 at 17:55
Is my observation correct, that the dictionary property (sorted alphabetically) doesn't help in the average case because the change in leading characters makes the order worthless?
– Vroomfondel
Nov 7 at 9:01
This is not the question, but have you considered using the levenshtein distance? It would compute it in O(N * L**2) (Accroding to your notations) which is assymptotically worse than your solution. But better in many cases (Since L < 26) .
– BlueSheepToken
Nov 8 at 14:21
What part of your contention are you unsure about, and why?
– Scott Hunter
Nov 3 at 2:09
What part of your contention are you unsure about, and why?
– Scott Hunter
Nov 3 at 2:09
The time complexity i'm unsure, it seems need to be bigger than linear but I can't convince myself of so.
– user1008636
Nov 3 at 2:12
The time complexity i'm unsure, it seems need to be bigger than linear but I can't convince myself of so.
– user1008636
Nov 3 at 2:12
Graph search is linear in the size of the graph when the (potential) out-degree is fixed, as here.
– Davis Herring
Nov 3 at 17:55
Graph search is linear in the size of the graph when the (potential) out-degree is fixed, as here.
– Davis Herring
Nov 3 at 17:55
Is my observation correct, that the dictionary property (sorted alphabetically) doesn't help in the average case because the change in leading characters makes the order worthless?
– Vroomfondel
Nov 7 at 9:01
Is my observation correct, that the dictionary property (sorted alphabetically) doesn't help in the average case because the change in leading characters makes the order worthless?
– Vroomfondel
Nov 7 at 9:01
This is not the question, but have you considered using the levenshtein distance? It would compute it in O(N * L**2) (Accroding to your notations) which is assymptotically worse than your solution. But better in many cases (Since L < 26) .
– BlueSheepToken
Nov 8 at 14:21
This is not the question, but have you considered using the levenshtein distance? It would compute it in O(N * L**2) (Accroding to your notations) which is assymptotically worse than your solution. But better in many cases (Since L < 26) .
– BlueSheepToken
Nov 8 at 14:21
add a comment |
2 Answers
2
active
oldest
votes
up vote
5
down vote
You won't find all simple paths because there might be alternative shortest paths to the end word. The simplest counterexample is as follows:
beginWord = aa,
endWord = bb
wordList = [aa, ab, ba, bb]
Your algorithm would miss the path aa -> ba -> bb
. In fact, it will always find at most one path.
The time complexity is indeed O(L * N)
as you wrote but the space complexity is O(L*N)
which is the space that your graph or wordList
occupies.
I think it does get all the paths. At each word in the path, graph contains a list of all transistions which is then handled by annoying recursive routine. Its hard to read from the poor typing and documentation.
– Charles Merriam
Nov 10 at 18:25
@CharlesMerriam By the time self.getAllPaths is being called graph is not built to include all transitions to a final word (as well as prior transitions). So, this recursive routine wouldn't work as it uses incomplete graph.
– Mikhail Berlinkov
Nov 10 at 19:51
An optimal solution to this problem would be as follows. In the first step, we compute distances to all words until we reach the endWord. In the second step, starting from endWord using DFS we traverse all paths going through words with distances decreasing by 1 until we reach beginWord.
– Mikhail Berlinkov
Nov 10 at 20:04
You are correct. There should be a flag, and then finish thefor word in q
loop before returning.
– Charles Merriam
Nov 11 at 0:49
@CharlesMerriam Unfortunately, this flag wouldn't help because as we miss other shortest paths to previous words in a path.
– Mikhail Berlinkov
Nov 11 at 13:46
|
show 3 more comments
up vote
0
down vote
This sounds like a fun problem. Yes, the answer is O(L * N)
. If you fixed your code to return all solutions, the recursive print routine is O(L!)
.
You have have this outer loop,
for all nodes being considered
. This can be equal to the length of your wordlist. Consider the fully connected set of three letter combinations['aaa', 'aab', ... 'zzz']
. The node count is 26^3, or 27576. Transforming fromaaa
tozzz
has six answers:aaa->zaa->zza->zzz
,aaa->zaa->aza->zzz
,aaa->aza->zza->zzz
, etc. You would be considering all length three paths, (25+25+25)(25+25)(25) or 93,750 paths to be sure there wasn't a shorter path.You have two choices for the inner loop:
for i in range(len(word))
and your recursive call toget_all_paths()
to list all the paths. You know you have an order of length_of_word for the inner, implyingO(L * N)
. Note thatO(L * N * 26)
means the same thing; big O notation only cares about the scale of changes. I haven't proved you stay linear on that get_all_paths loop.This is a special case of Dijkstra's Shortest Path. You can do better adding a heuristic to your specific problem. The total path length through a node is always greater than or equal to the distance so far plus the number of letters still wrong. That means, in the fully connected case, you have
aaa (0 length)->aab (1)->abb (2)->bbb (3)
so you avoid exploringaaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic)
.You can correct your code to return all the word ladders, and I did so here. The problem is that the recursive
getAllPaths()
routine now grows faster thenO(L * N)
. In the code sample, an input has two sets of "path choices", or subgraphs, set of which multiplies the number of paths. So, tripling the number of nodes would triple the number of path choices, cubing the number of path choices.
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
5
down vote
You won't find all simple paths because there might be alternative shortest paths to the end word. The simplest counterexample is as follows:
beginWord = aa,
endWord = bb
wordList = [aa, ab, ba, bb]
Your algorithm would miss the path aa -> ba -> bb
. In fact, it will always find at most one path.
The time complexity is indeed O(L * N)
as you wrote but the space complexity is O(L*N)
which is the space that your graph or wordList
occupies.
I think it does get all the paths. At each word in the path, graph contains a list of all transistions which is then handled by annoying recursive routine. Its hard to read from the poor typing and documentation.
– Charles Merriam
Nov 10 at 18:25
@CharlesMerriam By the time self.getAllPaths is being called graph is not built to include all transitions to a final word (as well as prior transitions). So, this recursive routine wouldn't work as it uses incomplete graph.
– Mikhail Berlinkov
Nov 10 at 19:51
An optimal solution to this problem would be as follows. In the first step, we compute distances to all words until we reach the endWord. In the second step, starting from endWord using DFS we traverse all paths going through words with distances decreasing by 1 until we reach beginWord.
– Mikhail Berlinkov
Nov 10 at 20:04
You are correct. There should be a flag, and then finish thefor word in q
loop before returning.
– Charles Merriam
Nov 11 at 0:49
@CharlesMerriam Unfortunately, this flag wouldn't help because as we miss other shortest paths to previous words in a path.
– Mikhail Berlinkov
Nov 11 at 13:46
|
show 3 more comments
up vote
5
down vote
You won't find all simple paths because there might be alternative shortest paths to the end word. The simplest counterexample is as follows:
beginWord = aa,
endWord = bb
wordList = [aa, ab, ba, bb]
Your algorithm would miss the path aa -> ba -> bb
. In fact, it will always find at most one path.
The time complexity is indeed O(L * N)
as you wrote but the space complexity is O(L*N)
which is the space that your graph or wordList
occupies.
I think it does get all the paths. At each word in the path, graph contains a list of all transistions which is then handled by annoying recursive routine. Its hard to read from the poor typing and documentation.
– Charles Merriam
Nov 10 at 18:25
@CharlesMerriam By the time self.getAllPaths is being called graph is not built to include all transitions to a final word (as well as prior transitions). So, this recursive routine wouldn't work as it uses incomplete graph.
– Mikhail Berlinkov
Nov 10 at 19:51
An optimal solution to this problem would be as follows. In the first step, we compute distances to all words until we reach the endWord. In the second step, starting from endWord using DFS we traverse all paths going through words with distances decreasing by 1 until we reach beginWord.
– Mikhail Berlinkov
Nov 10 at 20:04
You are correct. There should be a flag, and then finish thefor word in q
loop before returning.
– Charles Merriam
Nov 11 at 0:49
@CharlesMerriam Unfortunately, this flag wouldn't help because as we miss other shortest paths to previous words in a path.
– Mikhail Berlinkov
Nov 11 at 13:46
|
show 3 more comments
up vote
5
down vote
up vote
5
down vote
You won't find all simple paths because there might be alternative shortest paths to the end word. The simplest counterexample is as follows:
beginWord = aa,
endWord = bb
wordList = [aa, ab, ba, bb]
Your algorithm would miss the path aa -> ba -> bb
. In fact, it will always find at most one path.
The time complexity is indeed O(L * N)
as you wrote but the space complexity is O(L*N)
which is the space that your graph or wordList
occupies.
You won't find all simple paths because there might be alternative shortest paths to the end word. The simplest counterexample is as follows:
beginWord = aa,
endWord = bb
wordList = [aa, ab, ba, bb]
Your algorithm would miss the path aa -> ba -> bb
. In fact, it will always find at most one path.
The time complexity is indeed O(L * N)
as you wrote but the space complexity is O(L*N)
which is the space that your graph or wordList
occupies.
edited Nov 9 at 17:09
vencaslac
1,000217
1,000217
answered Nov 8 at 16:39
Mikhail Berlinkov
73446
73446
I think it does get all the paths. At each word in the path, graph contains a list of all transistions which is then handled by annoying recursive routine. Its hard to read from the poor typing and documentation.
– Charles Merriam
Nov 10 at 18:25
@CharlesMerriam By the time self.getAllPaths is being called graph is not built to include all transitions to a final word (as well as prior transitions). So, this recursive routine wouldn't work as it uses incomplete graph.
– Mikhail Berlinkov
Nov 10 at 19:51
An optimal solution to this problem would be as follows. In the first step, we compute distances to all words until we reach the endWord. In the second step, starting from endWord using DFS we traverse all paths going through words with distances decreasing by 1 until we reach beginWord.
– Mikhail Berlinkov
Nov 10 at 20:04
You are correct. There should be a flag, and then finish thefor word in q
loop before returning.
– Charles Merriam
Nov 11 at 0:49
@CharlesMerriam Unfortunately, this flag wouldn't help because as we miss other shortest paths to previous words in a path.
– Mikhail Berlinkov
Nov 11 at 13:46
|
show 3 more comments
I think it does get all the paths. At each word in the path, graph contains a list of all transistions which is then handled by annoying recursive routine. Its hard to read from the poor typing and documentation.
– Charles Merriam
Nov 10 at 18:25
@CharlesMerriam By the time self.getAllPaths is being called graph is not built to include all transitions to a final word (as well as prior transitions). So, this recursive routine wouldn't work as it uses incomplete graph.
– Mikhail Berlinkov
Nov 10 at 19:51
An optimal solution to this problem would be as follows. In the first step, we compute distances to all words until we reach the endWord. In the second step, starting from endWord using DFS we traverse all paths going through words with distances decreasing by 1 until we reach beginWord.
– Mikhail Berlinkov
Nov 10 at 20:04
You are correct. There should be a flag, and then finish thefor word in q
loop before returning.
– Charles Merriam
Nov 11 at 0:49
@CharlesMerriam Unfortunately, this flag wouldn't help because as we miss other shortest paths to previous words in a path.
– Mikhail Berlinkov
Nov 11 at 13:46
I think it does get all the paths. At each word in the path, graph contains a list of all transistions which is then handled by annoying recursive routine. Its hard to read from the poor typing and documentation.
– Charles Merriam
Nov 10 at 18:25
I think it does get all the paths. At each word in the path, graph contains a list of all transistions which is then handled by annoying recursive routine. Its hard to read from the poor typing and documentation.
– Charles Merriam
Nov 10 at 18:25
@CharlesMerriam By the time self.getAllPaths is being called graph is not built to include all transitions to a final word (as well as prior transitions). So, this recursive routine wouldn't work as it uses incomplete graph.
– Mikhail Berlinkov
Nov 10 at 19:51
@CharlesMerriam By the time self.getAllPaths is being called graph is not built to include all transitions to a final word (as well as prior transitions). So, this recursive routine wouldn't work as it uses incomplete graph.
– Mikhail Berlinkov
Nov 10 at 19:51
An optimal solution to this problem would be as follows. In the first step, we compute distances to all words until we reach the endWord. In the second step, starting from endWord using DFS we traverse all paths going through words with distances decreasing by 1 until we reach beginWord.
– Mikhail Berlinkov
Nov 10 at 20:04
An optimal solution to this problem would be as follows. In the first step, we compute distances to all words until we reach the endWord. In the second step, starting from endWord using DFS we traverse all paths going through words with distances decreasing by 1 until we reach beginWord.
– Mikhail Berlinkov
Nov 10 at 20:04
You are correct. There should be a flag, and then finish the
for word in q
loop before returning.– Charles Merriam
Nov 11 at 0:49
You are correct. There should be a flag, and then finish the
for word in q
loop before returning.– Charles Merriam
Nov 11 at 0:49
@CharlesMerriam Unfortunately, this flag wouldn't help because as we miss other shortest paths to previous words in a path.
– Mikhail Berlinkov
Nov 11 at 13:46
@CharlesMerriam Unfortunately, this flag wouldn't help because as we miss other shortest paths to previous words in a path.
– Mikhail Berlinkov
Nov 11 at 13:46
|
show 3 more comments
up vote
0
down vote
This sounds like a fun problem. Yes, the answer is O(L * N)
. If you fixed your code to return all solutions, the recursive print routine is O(L!)
.
You have have this outer loop,
for all nodes being considered
. This can be equal to the length of your wordlist. Consider the fully connected set of three letter combinations['aaa', 'aab', ... 'zzz']
. The node count is 26^3, or 27576. Transforming fromaaa
tozzz
has six answers:aaa->zaa->zza->zzz
,aaa->zaa->aza->zzz
,aaa->aza->zza->zzz
, etc. You would be considering all length three paths, (25+25+25)(25+25)(25) or 93,750 paths to be sure there wasn't a shorter path.You have two choices for the inner loop:
for i in range(len(word))
and your recursive call toget_all_paths()
to list all the paths. You know you have an order of length_of_word for the inner, implyingO(L * N)
. Note thatO(L * N * 26)
means the same thing; big O notation only cares about the scale of changes. I haven't proved you stay linear on that get_all_paths loop.This is a special case of Dijkstra's Shortest Path. You can do better adding a heuristic to your specific problem. The total path length through a node is always greater than or equal to the distance so far plus the number of letters still wrong. That means, in the fully connected case, you have
aaa (0 length)->aab (1)->abb (2)->bbb (3)
so you avoid exploringaaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic)
.You can correct your code to return all the word ladders, and I did so here. The problem is that the recursive
getAllPaths()
routine now grows faster thenO(L * N)
. In the code sample, an input has two sets of "path choices", or subgraphs, set of which multiplies the number of paths. So, tripling the number of nodes would triple the number of path choices, cubing the number of path choices.
add a comment |
up vote
0
down vote
This sounds like a fun problem. Yes, the answer is O(L * N)
. If you fixed your code to return all solutions, the recursive print routine is O(L!)
.
You have have this outer loop,
for all nodes being considered
. This can be equal to the length of your wordlist. Consider the fully connected set of three letter combinations['aaa', 'aab', ... 'zzz']
. The node count is 26^3, or 27576. Transforming fromaaa
tozzz
has six answers:aaa->zaa->zza->zzz
,aaa->zaa->aza->zzz
,aaa->aza->zza->zzz
, etc. You would be considering all length three paths, (25+25+25)(25+25)(25) or 93,750 paths to be sure there wasn't a shorter path.You have two choices for the inner loop:
for i in range(len(word))
and your recursive call toget_all_paths()
to list all the paths. You know you have an order of length_of_word for the inner, implyingO(L * N)
. Note thatO(L * N * 26)
means the same thing; big O notation only cares about the scale of changes. I haven't proved you stay linear on that get_all_paths loop.This is a special case of Dijkstra's Shortest Path. You can do better adding a heuristic to your specific problem. The total path length through a node is always greater than or equal to the distance so far plus the number of letters still wrong. That means, in the fully connected case, you have
aaa (0 length)->aab (1)->abb (2)->bbb (3)
so you avoid exploringaaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic)
.You can correct your code to return all the word ladders, and I did so here. The problem is that the recursive
getAllPaths()
routine now grows faster thenO(L * N)
. In the code sample, an input has two sets of "path choices", or subgraphs, set of which multiplies the number of paths. So, tripling the number of nodes would triple the number of path choices, cubing the number of path choices.
add a comment |
up vote
0
down vote
up vote
0
down vote
This sounds like a fun problem. Yes, the answer is O(L * N)
. If you fixed your code to return all solutions, the recursive print routine is O(L!)
.
You have have this outer loop,
for all nodes being considered
. This can be equal to the length of your wordlist. Consider the fully connected set of three letter combinations['aaa', 'aab', ... 'zzz']
. The node count is 26^3, or 27576. Transforming fromaaa
tozzz
has six answers:aaa->zaa->zza->zzz
,aaa->zaa->aza->zzz
,aaa->aza->zza->zzz
, etc. You would be considering all length three paths, (25+25+25)(25+25)(25) or 93,750 paths to be sure there wasn't a shorter path.You have two choices for the inner loop:
for i in range(len(word))
and your recursive call toget_all_paths()
to list all the paths. You know you have an order of length_of_word for the inner, implyingO(L * N)
. Note thatO(L * N * 26)
means the same thing; big O notation only cares about the scale of changes. I haven't proved you stay linear on that get_all_paths loop.This is a special case of Dijkstra's Shortest Path. You can do better adding a heuristic to your specific problem. The total path length through a node is always greater than or equal to the distance so far plus the number of letters still wrong. That means, in the fully connected case, you have
aaa (0 length)->aab (1)->abb (2)->bbb (3)
so you avoid exploringaaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic)
.You can correct your code to return all the word ladders, and I did so here. The problem is that the recursive
getAllPaths()
routine now grows faster thenO(L * N)
. In the code sample, an input has two sets of "path choices", or subgraphs, set of which multiplies the number of paths. So, tripling the number of nodes would triple the number of path choices, cubing the number of path choices.
This sounds like a fun problem. Yes, the answer is O(L * N)
. If you fixed your code to return all solutions, the recursive print routine is O(L!)
.
You have have this outer loop,
for all nodes being considered
. This can be equal to the length of your wordlist. Consider the fully connected set of three letter combinations['aaa', 'aab', ... 'zzz']
. The node count is 26^3, or 27576. Transforming fromaaa
tozzz
has six answers:aaa->zaa->zza->zzz
,aaa->zaa->aza->zzz
,aaa->aza->zza->zzz
, etc. You would be considering all length three paths, (25+25+25)(25+25)(25) or 93,750 paths to be sure there wasn't a shorter path.You have two choices for the inner loop:
for i in range(len(word))
and your recursive call toget_all_paths()
to list all the paths. You know you have an order of length_of_word for the inner, implyingO(L * N)
. Note thatO(L * N * 26)
means the same thing; big O notation only cares about the scale of changes. I haven't proved you stay linear on that get_all_paths loop.This is a special case of Dijkstra's Shortest Path. You can do better adding a heuristic to your specific problem. The total path length through a node is always greater than or equal to the distance so far plus the number of letters still wrong. That means, in the fully connected case, you have
aaa (0 length)->aab (1)->abb (2)->bbb (3)
so you avoid exploringaaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic)
.You can correct your code to return all the word ladders, and I did so here. The problem is that the recursive
getAllPaths()
routine now grows faster thenO(L * N)
. In the code sample, an input has two sets of "path choices", or subgraphs, set of which multiplies the number of paths. So, tripling the number of nodes would triple the number of path choices, cubing the number of path choices.
edited Nov 13 at 9:04
answered Nov 10 at 18:23
Charles Merriam
10.9k55261
10.9k55261
add a comment |
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53075364%2ftime-complexity-of-this-algorithm-word-ladder%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
What part of your contention are you unsure about, and why?
– Scott Hunter
Nov 3 at 2:09
The time complexity i'm unsure, it seems need to be bigger than linear but I can't convince myself of so.
– user1008636
Nov 3 at 2:12
Graph search is linear in the size of the graph when the (potential) out-degree is fixed, as here.
– Davis Herring
Nov 3 at 17:55
Is my observation correct, that the dictionary property (sorted alphabetically) doesn't help in the average case because the change in leading characters makes the order worthless?
– Vroomfondel
Nov 7 at 9:01
This is not the question, but have you considered using the levenshtein distance? It would compute it in O(N * L**2) (Accroding to your notations) which is assymptotically worse than your solution. But better in many cases (Since L < 26) .
– BlueSheepToken
Nov 8 at 14:21