Minimum Number of Valid Strings to Form Target II

HARD

Description

You are given an array of strings words and a string target.

A string x is called valid if x is a prefix of any string in words.

Return the minimum number of valid strings that can be concatenated to form target. If it is not possible to form target, return -1.

 

Example 1:

Input: words = ["abc","aaaaa","bcdef"], target = "aabcdabc"

Output: 3

Explanation:

The target string can be formed by concatenating:

  • Prefix of length 2 of words[1], i.e. "aa".
  • Prefix of length 3 of words[2], i.e. "bcd".
  • Prefix of length 3 of words[0], i.e. "abc".

Example 2:

Input: words = ["abababab","ab"], target = "ababaababa"

Output: 2

Explanation:

The target string can be formed by concatenating:

  • Prefix of length 5 of words[0], i.e. "ababa".
  • Prefix of length 5 of words[0], i.e. "ababa".

Example 3:

Input: words = ["abcdef"], target = "xyz"

Output: -1

 

Constraints:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 5 * 104
  • The input is generated such that sum(words[i].length) <= 105.
  • words[i] consists only of lowercase English letters.
  • 1 <= target.length <= 5 * 104
  • target consists only of lowercase English letters.

Approaches

Checkout 2 different approaches to solve Minimum Number of Valid Strings to Form Target II. Click on different approaches to view the approach and algorithm in detail.

Dynamic Programming with Naive Prefix Checking

This approach uses dynamic programming to solve the problem. We define dp[i] as the minimum number of valid strings required to form the prefix of target of length i. The goal is to compute dp[n], where n is the length of target.

The base case is dp[0] = 0, as an empty string requires zero valid strings. For each i from 1 to n, we try to find a split point j (where 0 <= j < i) such that the substring target[j...i-1] is a valid prefix. If it is, we can potentially form target[0...i-1] by taking an optimal solution for target[0...j-1] (which is dp[j]) and adding one more valid string, target[j...i-1].

To check if target[j...i-1] is a valid prefix, we perform a naive search: iterate through every word in the words array and use the startsWith method. We take the minimum over all possible split points j.

Algorithm

  1. Let n be the length of the target string.
  2. Create a dynamic programming array dp of size n + 1. dp[i] will store the minimum number of valid strings to form the prefix target[0...i-1].
  3. Initialize dp[0] = 0 (an empty prefix requires 0 strings) and all other dp[i] to a value representing infinity (e.g., n + 1).
  4. Iterate from i = 1 to n: a. For each i, iterate from j = 0 to i - 1. b. Extract the substring sub = target.substring(j, i). c. Check if sub is a valid prefix. This is done by iterating through every word in the words array and checking if word.startsWith(sub). d. If sub is a valid prefix and dp[j] is not infinity, it means we can form target[0...i-1] by appending sub to a valid formation of target[0...j-1]. Update dp[i] = min(dp[i], dp[j] + 1).
  5. After the loops complete, if dp[n] is still infinity, it's impossible to form the target. Return -1.
  6. Otherwise, dp[n] holds the minimum number of strings, so return dp[n].

The algorithm iterates through all possible ending positions i in the target string and for each i, it considers all possible starting positions j. The substring between j and i is then checked for validity. This validity check is the main performance bottleneck, as it involves iterating through the entire words array for every single substring. This leads to a very high polynomial time complexity.

public int minStrings(String[] words, String target) {
    int n = target.length();
    int[] dp = new int[n + 1];
    java.util.Arrays.fill(dp, n + 1);
    dp[0] = 0;

    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < i; j++) {
            if (dp[j] > n) {
                continue;
            }
            String sub = target.substring(j, i);
            boolean is_valid = false;
            for (String word : words) {
                if (word.startsWith(sub)) {
                    is_valid = true;
                    break;
                }
            }

            if (is_valid) {
                dp[i] = Math.min(dp[i], dp[j] + 1);
            }
        }
    }

    return dp[n] > n ? -1 : dp[n];
}

Complexity Analysis

Time Complexity: O(n³ * m * L_avg), where `n` is `target.length`, `m` is `words.length`, and `L_avg` is the average length of a word. The nested loops for `i` and `j` give `O(n²)`, substring extraction can take `O(n)`, and the validity check takes `O(m * L_avg)`. A tighter bound is `O(n² * S)` where S is the total length of all words, as `sum(check_time) = sum(m*len(sub))`, which is dominated by `O(S)` for each `(i,j)` pair. A simple analysis gives `O(n^3 * m)`.Space Complexity: O(n), where `n` is the length of the `target` string. This is for the `dp` array.

Pros and Cons

Pros:
  • Simple to understand and implement.
  • Correctly solves the problem for small inputs.
Cons:
  • Extremely inefficient due to repeated and slow prefix checks.
  • The time complexity makes it infeasible for the given constraints, leading to a Time Limit Exceeded (TLE) error.

Code Solutions

Checking out 3 solutions in different languages for Minimum Number of Valid Strings to Form Target II. Click on different languages to view the code.

class Hashing { private final long [] p ; private final long [] h ; private final long mod ; public Hashing ( String word , long base , int mod ) { int n = word . length (); p = new long [ n + 1 ]; h = new long [ n + 1 ]; p [ 0 ] = 1 ; this . mod = mod ; for ( int i = 1 ; i <= n ; i ++) { p [ i ] = p [ i - 1 ] * base % mod ; h [ i ] = ( h [ i - 1 ] * base + word . charAt ( i - 1 )) % mod ; } } public long query ( int l , int r ) { return ( h [ r ] - h [ l - 1 ] * p [ r - l + 1 ] % mod + mod ) % mod ; } } class Solution { private Hashing hashing ; private Set < Long >[] s ; public int minValidStrings ( String [] words , String target ) { int base = 13331 , mod = 998244353 ; hashing = new Hashing ( target , base , mod ); int m = Arrays . stream ( words ). mapToInt ( String: : length ). max (). orElse ( 0 ); s = new Set [ m + 1 ]; Arrays . setAll ( s , k -> new HashSet <>()); for ( String w : words ) { long h = 0 ; for ( int j = 0 ; j < w . length (); j ++) { h = ( h * base + w . charAt ( j )) % mod ; s [ j + 1 ]. add ( h ); } } int ans = 0 ; int last = 0 ; int mx = 0 ; int n = target . length (); for ( int i = 0 ; i < n ; i ++) { int dist = f ( i , n , m ); mx = Math . max ( mx , i + dist ); if ( i == last ) { if ( i == mx ) { return - 1 ; } last = mx ; ans ++; } } return ans ; } private int f ( int i , int n , int m ) { int l = 0 , r = Math . min ( n - i , m ); while ( l < r ) { int mid = ( l + r + 1 ) >> 1 ; long sub = hashing . query ( i + 1 , i + mid ); if ( s [ mid ]. contains ( sub )) { l = mid ; } else { r = mid - 1 ; } } return l ; } }

Video Solution

Watch the video walkthrough for Minimum Number of Valid Strings to Form Target II



Algorithms:

Binary Search

Patterns:

Dynamic ProgrammingString MatchingRolling HashHash Function

Data Structures:

ArrayStringSegment Tree

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.