-1

Can someone please help me with the time and space complexity of this code snippet? Please refer to the leetcode question- Word break ii.Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add spaces in s to construct a sentence where each word is a valid dictionary word. Return all such possible sentences.

def wordBreak(self, s, wordDict):
    dp = {}
    def word_break(s):
        if s in dp:
            return dp[s]
        result = []
        for w in wordDict:
            if s[:len(w)] == w:
                if len(w) == len(s):
                    result.append(w)
                else:
                    tmp = word_break(s[len(w):])
                    for t in tmp:
                        result.append(w + " " + t)
        dp[s] = result
        return result
    
    return word_break(s)
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
curiousD
  • 27
  • 6
  • 1
    Did you try anything yourself? Also, I'd suggest using different code formatting - it's hard to read when you use in-line `if` but multiline `else`. – NotAName Oct 21 '20 at 01:44
  • 1
    I feel it's # of word*avg len of word * the entire string or n^2 where n is no of total characters in the string (including duplicates) – curiousD Oct 21 '20 at 01:52
  • 1
    Ok, searching a substring in string using `in` operator is sublinear in the best-case and quadratic in the worst-case. Then iterating over keys of dictionary is O(n). Plus there are some comparisons and slicing, but it feels like overall the asymptotic complexity should be O(n^2). – NotAName Oct 21 '20 at 02:09

2 Answers2

1

Decision Tree

enter image description here

Time Complexity

  • height of tree is len(target)=m in worst case
  • branching is len(words)=n
  • in each recursive call, you make slicing if s[:len(w)]. worst case is m
  • T: O(N^m * M)=O(n^m)

Space complexity

  • space from call stack is the height of tree=m
  • IN each recursive call you are making suffix and storing it. its length is m
  • S : O(m*m)
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
0
  • For time complexity analysis, almost always we'd take the worst case scenario into account.

  • I guess O(N ^ 3) runtime and O(N) memory would be right.

  • Here is a breakdown:

def wordBreak(self, s, wordDict):
    dp = {}
    def word_break(s):
        # This block would be O(N) 
        if s in dp: # order of N
            return dp[s]
        result = []

        # This block would be O(N ^ 2)
        for w in wordDict: # order of N
            if s[:len(w)] == w:
                if len(w) == len(s):
                    result.append(w)
                else:
                    tmp = word_break(s[len(w):])
                    for t in tmp: # order of N
                        result.append(w + " " + t)
        dp[s] = result
        return result
    
    return word_break(s) # This'd be O(N) itself
  • Therefore, O(N ^ 2) of the second block is multiplied by O(N) of calling the word_break(s) which would eventually make it O(N ^ 3).

  • We can a bit simplify it to:

class Solution:
    def wordBreak(self, s, words):
        dp = [False] * len(s)
        for i in range(len(s)): # order of N
            for word in words:  # order of N
                k = i - len(word)
                if word == s[k + 1:i + 1] and (dp[k] or k == -1):  # order of N
                    dp[i] = True
        return dp[-1]

which would make it easier to see:

  • for i in range(len(s)): is an order of N;
    • for word in words: is an order of N;
      • if word == s[k + 1:i + 1] and (dp[k] or k == -1): is an order of N;
Emma
  • 27,428
  • 11
  • 44
  • 69
  • Shouldn't the second block be O(N)? Since it is only iterating through the string once- by iterating through the words and their individual characters. – curiousD Oct 21 '20 at 18:58