Time complexity of this algorithm: Word Ladder











up vote
8
down vote

favorite
1












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)










share|improve this question
























  • 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















up vote
8
down vote

favorite
1












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)










share|improve this question
























  • 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













up vote
8
down vote

favorite
1









up vote
8
down vote

favorite
1






1





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)










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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


















  • 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












2 Answers
2






active

oldest

votes

















up vote
5
down vote



+25










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.






share|improve this answer























  • 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 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


















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!).




  1. 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 from aaa to zzz 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.


  2. You have two choices for the inner loop: for i in range(len(word)) and your recursive call to get_all_paths() to list all the paths. You know you have an order of length_of_word for the inner, implying O(L * N). Note that O(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.


  3. 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 exploring aaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic).


  4. 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 then O(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.







share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














     

    draft saved


    draft discarded


















    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

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    5
    down vote



    +25










    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.






    share|improve this answer























    • 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 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















    up vote
    5
    down vote



    +25










    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.






    share|improve this answer























    • 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 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













    up vote
    5
    down vote



    +25







    up vote
    5
    down vote



    +25




    +25




    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.






    share|improve this answer














    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.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    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 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


















    • 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 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
















    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












    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!).




    1. 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 from aaa to zzz 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.


    2. You have two choices for the inner loop: for i in range(len(word)) and your recursive call to get_all_paths() to list all the paths. You know you have an order of length_of_word for the inner, implying O(L * N). Note that O(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.


    3. 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 exploring aaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic).


    4. 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 then O(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.







    share|improve this answer



























      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!).




      1. 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 from aaa to zzz 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.


      2. You have two choices for the inner loop: for i in range(len(word)) and your recursive call to get_all_paths() to list all the paths. You know you have an order of length_of_word for the inner, implying O(L * N). Note that O(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.


      3. 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 exploring aaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic).


      4. 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 then O(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.







      share|improve this answer

























        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!).




        1. 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 from aaa to zzz 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.


        2. You have two choices for the inner loop: for i in range(len(word)) and your recursive call to get_all_paths() to list all the paths. You know you have an order of length_of_word for the inner, implying O(L * N). Note that O(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.


        3. 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 exploring aaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic).


        4. 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 then O(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.







        share|improve this answer














        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!).




        1. 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 from aaa to zzz 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.


        2. You have two choices for the inner loop: for i in range(len(word)) and your recursive call to get_all_paths() to list all the paths. You know you have an order of length_of_word for the inner, implying O(L * N). Note that O(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.


        3. 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 exploring aaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic).


        4. 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 then O(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.








        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 13 at 9:04

























        answered Nov 10 at 18:23









        Charles Merriam

        10.9k55261




        10.9k55261






























             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            Florida Star v. B. J. F.

            Danny Elfman

            Lugert, Oklahoma