Back to blog
Nov 28, 2024
4 min read

LeetCode 139: Word Break

Leetcode 139: Word Break solution in Python

Problem Description

LeetCode Problem 139

Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words.

Note that the same word in the dictionary may be reused multiple times in the segmentation.

 

Example 1:

Input: s = “leetcode”, wordDict = [“leet”,“code”] Output: true Explanation: Return true because “leetcode” can be segmented as “leet code”.

Example 2:

Input: s = “applepenapple”, wordDict = [“apple”,“pen”] Output: true Explanation: Return true because “applepenapple” can be segmented as “apple pen apple”. Note that you are allowed to reuse a dictionary word.

Example 3:

Input: s = “catsandog”, wordDict = [“cats”,“dog”,“sand”,“and”,“cat”] Output: false

 

Constraints:

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • s and wordDict[i] consist of only lowercase English letters.
  • All the strings of wordDict are unique.

Difficulty: Medium

Tags: array, hash table, string, dynamic programming, trie, memoization

Rating: 95.51%

The Dynamic Programming Solution

Let’s break down the solution step by step:

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        # Convert wordDict to a set for O(1) lookups
        words = set(wordDict)
        
        # dp[i] represents if s[0:i] can be segmented using dictionary words
        dp = [False] * (len(s) + 1)
        
        # Empty string is always valid
        dp[0] = True
        
        # For each end position in the string
        for r in range(1, len(s) + 1):
            # Try all possible starting positions for the last word
            for l in range(r):
                # If we can form s[0:l] AND s[l:r] is in dictionary
                if dp[l] and s[l:r] in words:
                    dp[r] = True
                    break
        
        return dp[-1]

How the Algorithm Works

Let’s walk through an example with s = “leetcode” and wordDict = [“leet”, “code”]:

  1. Initialize dp array: [True, False, False, False, False, False, False, False, False]
  2. For r = 1 (“l”):
    • Check substrings: “l” → not in dictionary
    • dp[1] remains False
  3. For r = 4 (“leet”):
    • Check “leet” → in dictionary and dp[0] is True
    • dp[4] becomes True
  4. For r = 8 (“leetcode”):
    • When l = 4, we find that:
      • dp[4] is True (we can form “leet”)
      • “code” is in dictionary
    • Therefore, dp[8] becomes True

Complexity Analysis

  • Time Complexity: O(n2)O(n²)

    • We have a nested loop structure
    • Outer loop runs n times
    • Inner loop can run up to current position
    • String slicing and set lookup are O(1)
  • Space Complexity: O(n)O(n)

    • We only need the dp array of size n+1
    • The word set takes additional space but is bounded by the dictionary size

Common Pitfalls and Optimization Tips

  1. Converting to Set: Always convert the wordDict to a set for O(1) lookups. Using a list would result in O(n) lookups.
  2. Breaking Early: The break statement is crucial for efficiency - once we find a valid segmentation for a position, we don’t need to check other possibilities.
  3. Empty String Case: Handling the empty string case by setting dp[0] = True is essential for the algorithm to work correctly.

Additional Notes

  • This solution is particularly efficient for shorter strings and moderate-sized dictionaries
  • For very large strings or dictionaries, a trie-based solution might be more appropriate