Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Time complexity of this algorithm: Word Ladder

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)

like image 302
user1008636 Avatar asked Oct 31 '18 02:10

user1008636


People also ask

How do you find the time complexity of an algorithm?

Time Complexity is most commonly estimated by counting the number of elementary steps performed by any algorithm to finish execution. Like in the example above, for the first code the loop will run n number of times, so the time complexity will be n atleast and as the value of n will increase the time taken will also increase.

Why do we use worst-case time complexity of an algorithm?

And since the algorithm's performance may vary with different types of input data, hence for an algorithm we usually use the worst-case Time complexity of an algorithm because that is the maximum time taken for any input size. Now lets tap onto the next big topic related to Time complexity, which is How to Calculate Time Complexity.

What is the time complexity notation?

It's an asymptotic notation to represent the time complexity. We will study about it in detail in the next tutorial. Time Complexity is most commonly estimated by counting the number of elementary steps performed by any algorithm to finish execution.

What is the running time of the algorithm?

The running time consists of N loops (iterative or recursive) that are logarithmic, thus the algorithm is a combination of linear and logarithmic. NOTE: In general, doing something with every item in one dimension is linear, doing something with every item in two dimensions is quadratic, and dividing the working area in half is logarithmic.


2 Answers

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.

like image 53
Mikhail Berlinkov Avatar answered Oct 04 '22 17:10

Mikhail Berlinkov


the answer should be O(L^2 * n)

In the process of building a new word, it costs O(L^2) in total. Firstly we loop the current word, that costs O(L); then for building each new string: newWord = word[:i] + sub + word[i+1:], this cost another O(L)

like image 22
Zhenyi Lin Avatar answered Oct 04 '22 15:10

Zhenyi Lin