Algorithm for checking if a string was built from a list of substrings

前端 未结 10 1999
醉酒成梦
醉酒成梦 2021-02-02 14:35

You are given a string and an array of strings. How to quickly check, if this string can be built by concatenating some of the strings in the array?

This is a theoretica

10条回答
  •  灰色年华
    2021-02-02 15:08

    Note: I assume here that you can use each substring more than once. You can generalize the solution to include this restriction by changing how we define subproblems. That will have a negative impact on space as well as expected runtime, but the problem remains polynomial.

    This is a dynamic programming problem. (And a great question!)

    Let's define composable(S, W) to be true if the string S can be written using the list of substrings W.

    S is composable if and only if:

    1. S starts with a substring w in W.
    2. The remainder of S after w is also composable.

    Let's write some pseudocode:

    COMPOSABLE(S, W):
      return TRUE if S = "" # Base case
      return memo[S] if memo[S]
    
      memo[S] = false
    
      for w in W:
        length <- LENGTH(w)
        start  <- S[1..length]
        rest   <- S[length+1..-1]
        if start = w AND COMPOSABLE(rest, W) :
          memo[S] = true # Memoize
    
      return memo[S]
    

    This algorithm has O(m*n) runtime, assuming the length of the substrings is not linear w/r/t to the string itself, in which case runtime would be O(m*n^2) (where m is the size of the substring list and n is the length of the string in question). It uses O(n) space for memoization.

    (N.B. as written the pseudocode uses O(n^2) space, but hashing the memoization keys would alleviate this.)

    EDIT

    Here is a working Ruby implementation:

    def composable(str, words)
      composable_aux(str, words, {})
    end
    
    def composable_aux(str, words, memo)
      return true if str == ""                # The base case
      return memo[str] unless memo[str].nil?  # Return the answer if we already know it
    
      memo[str] = false              # Assume the answer is `false`
    
      words.each do |word|           # For each word in the list:
        length = word.length
        start  = str[0..length-1]
        rest   = str[length..-1]
    
        # If the test string starts with this word,
        # and the remaining part of the test string
        # is also composable, the answer is true.
        if start == word and composable_aux(rest, words, memo)
          memo[str] = true           # Mark the answer as true
        end
      end
    
      memo[str]                      # Return the answer
    end
    

提交回复
热议问题