Talentd
FleetCode
Make and follow sheets.Sign in

Minimum Number of Valid Strings to Form Target II solution and explanation

HardArrayBinary SearchDynamic ProgrammingHash Function8 min read21.1% acceptanceUpdated 9 Apr 2026
3292. Minimum Number of Valid Strings to Form Target II | Weekly Leetcode 415 | Z-AlgorithmcodingMohan · 51:11 · 1K viewsWatch on YouTube
1 more video on this problem
  1. 3292. Minimum Number of Valid Strings to Form Target II | Weekly Leetcode 415 | Z-AlgorithmcodingMohan · 51:11 · 1K views
  2. 3292. Minimum Number of Valid Strings to Form Target II (Leetcode Hard)Programming Live with Larry · 55:24 · 388 views

The problem

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.

Approach overview

Problem Overview: You are given a list of valid strings and a target string. The goal is to form the target by concatenating valid strings (or their prefixes depending on constraints) while minimizing the total number of strings used. If the target cannot be formed, return -1. The challenge is efficiently checking which substrings of the target match valid strings while minimizing the number of segments used.

Approach 1: Dynamic Programming with Rolling Hash (O(n log n) time, O(n) space)

The core idea is to treat the problem as a segmentation task. Define dp[i] as the minimum number of valid strings required to build the prefix target[0..i). For each position, attempt to extend using any valid substring that matches the target starting at that index. Direct string comparison would be too slow, so use rolling hash to check substring equality in constant time. Precompute hashes for the target and store hashes of valid strings by length. For each index, binary search the maximum valid match length and update the DP transition. This reduces repeated comparisons and keeps substring checks efficient.

This approach combines dynamic programming for optimal substructure and string matching techniques for fast substring validation. The DP ensures minimal segmentation while rolling hash eliminates expensive character-by-character checks.

Approach 2: Greedy with Backtracking (O(n^2) worst case time, O(n) space)

A simpler approach attempts to greedily match the longest valid string starting at the current position. If multiple matches exist, choose the longest candidate and recursively attempt to build the remainder of the target. When a greedy choice fails later, backtrack and try the next candidate. Memoization can prune repeated states by caching results for each index.

This strategy works because longer matches often reduce the total number of segments. However, greedy choices are not always globally optimal, so backtracking ensures correctness. Matching can be accelerated with prefix comparisons or pre-indexed candidate lengths. Compared to the DP solution, this method is easier to implement but may degrade in performance for large inputs with many overlapping prefixes.

The greedy/backtracking strategy relies heavily on fast prefix checks and benefits from techniques like rolling hash or prefix maps for efficient substring validation.

Recommended for interviews: The dynamic programming approach with hashing is the expected optimal solution. It demonstrates understanding of segmentation DP, efficient substring matching, and complexity optimization. Explaining the greedy strategy first helps show intuition about minimizing segments, but the DP formulation proves you can guarantee the optimal result under strict constraints.

Approach 1: Dynamic Programming Approach

This approach involves using dynamic programming to determine the minimum number of valid string prefixes needed to form the target string. We define a DP array where each state represents the minimum number of prefixes required to form a prefix of the target of a given length.

def min_valid_strings_to_form_target(words, target):
    n = len(target)
    dp = [float('inf')] * (n + 1)
    dp[0] = 0
    words_set = set(words)

    for i in range(1, n + 1):
        for j in range(i):
            if target[j:i] in words_set:
                dp[i] = min(dp[i], dp[j] + 1)

    return -1 if dp[n] == float('inf') else dp[n]
#include <string>
#include <vector>
#include <unordered_set>

int minValidStringsToFormTarget(std::vector<std::string>& words, std::string target) {
    int n = target.size();
    std::vector<int> dp(n + 1, INT_MAX);
    dp[0] = 0;
    std::unordered_set<std::string> wordsSet(words.begin(), words.end());

    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < i; ++j) {
            if (wordsSet.count(target.substr(j, i - j))) {
                dp[i] = std::min(dp[i], dp[j] + 1);
            }
        }
    }

    return dp[n] == INT_MAX ? -1 : dp[n];
}
function minValidStringsToFormTarget(words, target) {
    const n = target.length;
    const dp = new Array(n + 1).fill(Infinity);
    dp[0] = 0;
    const wordsSet = new Set(words);

    for (let i = 1; i <= n; i++) {
        for (let j = 0; j < i; j++) {
            if (wordsSet.has(target.substring(j, i))) {
                dp[i] = Math.min(dp[i], dp[j] + 1);
            }
        }
    }

    return dp[n] === Infinity ? -1 : dp[n];
}

We initialize a DP array to store the minimum number of prefixes required for each prefix length of the target. For each end position of a substring of the target, we check all possible starting positions. If the substring is in the set of words, we update the DP table.

Complexity

Time Complexity: O(n2) where n is the length of the target string.
Space Complexity: O(n) for the DP array.

Try this approach in the editor

Approach 2: Greedy Approach with Backtracking

This approach leverages a backtracking strategy to identify valid prefixes greedily, trying to cut the target string into chunks from the longest valid prefix available and backtracking when necessary.

def min_valid_strings_backtrack(words, target):
    def backtrack(index, count):
        if index == len(target):
            return count
        min_count = float('inf')
        for word in words:
            if target.startswith(word, index):
                result = backtrack(index + len(word), count + 1)
                min_count = min(min_count, result)
        return min_count
    result = backtrack(0, 0)
    return -1 if result == float('inf') else result
function minValidStringsBacktrack(words, target) {
    function backtrack(index, count) {
        if (index === target.length) return count;
        let minCount = Infinity;
        for (const word of words) {
            if (target.startsWith(word, index)) {
                const result = backtrack(index + word.length, count + 1);
                minCount = Math.min(minCount, result);
            }
        }
        return minCount;
    }

    const result = backtrack(0, 0);
    return result === Infinity ? -1 : result;
}

This solution uses a recursive function with backtracking to explore all possible ways to split the target into valid prefixes. It terminates on the successful attempt that minimizes the count.

Complexity

Time Complexity: Exponential in the worst case, depending on branching factors.
Space Complexity: O(m) where m is the recursion stack depth.

Try this approach in the editor

Approach 3: String Hashing + Binary Search + Greedy

Due to the large data scale of this problem, using the "Trie + Memoization" method will time out. We need to find a more efficient solution.

Consider starting from the i-th character of the string target and finding the maximum matching substring length, denoted as dist. For any j \in [i, i + dist - 1], we can find a string in words such that target[i..j] is a prefix of this string. This has a monotonic property, so we can use binary search to determine dist.

Specifically, we first preprocess the hash values of all prefixes of strings in words and store them in the array s grouped by prefix length. Additionally, we preprocess the hash values of target and store them in hashing to facilitate querying the hash value of any target[l..r].

Next, we design a function f(i) to represent the maximum matching substring length starting from the i-th character of the string target. We can determine f(i) using binary search.

Define the left boundary of the binary search as l = 0 and the right boundary as r = min(n - i, m), where n is the length of the string target and m is the maximum length of strings in words. During the binary search, we need to check if target[i..i+mid-1] is one of the hash values in s[mid]. If it is, update the left boundary l to mid; otherwise, update the right boundary r to mid - 1. After the binary search, return l.

After calculating f(i), the problem becomes a classic greedy problem. Starting from i = 0, for each position i, the farthest position we can move to is i + f(i). We need to find the minimum number of moves to reach the end.

We define last to represent the last moved position and mx to represent the farthest position we can move to from the current position. Initially, last = mx = 0. We traverse from i = 0. If i equals last, it means we need to move again. If last = mx, it means we cannot move further, so we return -1. Otherwise, we update last to mx and increment the answer by one.

After the traversal, return the answer.

The time complexity is O(n times log n + L), and the space complexity is O(n + L). Here, n is the length of the string target, and L is the total length of all valid strings.

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;
    }
}
function minValidStrings(words: string[], target: string): number {
    class Hashing {
        private p: bigint[];
        private h: bigint[];
        private mod: bigint;

        constructor(word: string, base: bigint, mod: bigint) {
            const n = word.length;
            this.p = new Array<bigint>(n + 1).fill(0n);
            this.h = new Array<bigint>(n + 1).fill(0n);
            this.mod = mod;
            this.p[0] = 1n;
            for (let i = 1; i <= n; ++i) {
                this.p[i] = (this.p[i - 1] * base) % mod;
                this.h[i] = (this.h[i - 1] * base + BigInt(word.charCodeAt(i - 1))) % mod;
            }
        }

        query(l: number, r: number): bigint {
            const res =
                (this.h[r] - ((this.h[l - 1] * this.p[r - l + 1]) % this.mod) + this.mod) %
                this.mod;
            return res;
        }
    }

    const base = 13331n;
    const mod = 998244353n;
    const hashing = new Hashing(target, base, mod);

    const m = Math.max(0, ...words.map(w => w.length));
    const s: Set<bigint>[] = Array.from({ length: m + 1 }, () => new Set<bigint>());

    for (const w of words) {
        let h = 0n;
        for (let j = 0; j < w.length; ++j) {
            h = (h * base + BigInt(w.charCodeAt(j))) % mod;
            s[j + 1].add(h);
        }
    }

    const n = target.length;
    let ans = 0;
    let last = 0;
    let mx = 0;

    const f = (i: number): number => {
        let l = 0;
        let r = Math.min(n - i, m);
        while (l < r) {
            const mid = (l + r + 1) >> 1;
            const sub = hashing.query(i + 1, i + mid);
            if (s[mid].has(sub)) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }
        return l;
    };

    for (let i = 0; i < n; ++i) {
        const dist = f(i);
        mx = Math.max(mx, i + dist);
        if (i === last) {
            if (i === mx) {
                return -1;
            }
            last = mx;
            ans++;
        }
    }

    return ans;
}
type Hashing struct {
	p   []int64
	h   []int64
	mod int64
}

func NewHashing(word string, base int64, mod int64) *Hashing {
	n := len(word)
	p := make([]int64, n+1)
	h := make([]int64, n+1)
	p[0] = 1
	for i := 1; i <= n; i++ {
		p[i] = (p[i-1] * base) % mod
		h[i] = (h[i-1]*base + int64(word[i-1])) % mod
	}
	return &Hashing{p, h, mod}
}

func (hashing *Hashing) Query(l, r int) int64 {
	return (hashing.h[r] - hashing.h[l-1]*hashing.p[r-l+1]%hashing.mod + hashing.mod) % hashing.mod
}

func minValidStrings(words []string, target string) (ans int) {
	base, mod := int64(13331), int64(998244353)
	hashing := NewHashing(target, base, mod)

	m, n := 0, len(target)
	for _, w := range words {
		m = max(m, len(w))
	}

	s := make([]map[int64]bool, m+1)

	f := func(i int) int {
		l, r := 0, int(math.Min(float64(n-i), float64(m)))
		for l < r {
			mid := (l + r + 1) >> 1
			sub := hashing.Query(i+1, i+mid)
			if s[mid][sub] {
				l = mid
			} else {
				r = mid - 1
			}
		}
		return l
	}

	for _, w := range words {
		h := int64(0)
		for j := 0; j < len(w); j++ {
			h = (h*base + int64(w[j])) % mod
			if s[j+1] == nil {
				s[j+1] = make(map[int64]bool)
			}
			s[j+1][h] = true
		}
	}

	var last, mx int

	for i := 0; i < n; i++ {
		dist := f(i)
		mx = max(mx, i+dist)
		if i == last {
			if i == mx {
				return -1
			}
			last = mx
			ans++
		}
	}
	return ans
}
use std::collections::HashSet;
use std::cmp::max;

struct Hashing {
    p: Vec<i64>,
    h: Vec<i64>,
    base: i64,
    modv: i64,
}

impl Hashing {
    fn new(word: &str, base: i64, modv: i64) -> Self {
        let n = word.len();
        let mut p = vec![0; n + 1];
        let mut h = vec![0; n + 1];
        let bytes = word.as_bytes();
        p[0] = 1;
        for i in 1..=n {
            p[i] = p[i - 1] * base % modv;
            h[i] = (h[i - 1] * base + bytes[i - 1] as i64) % modv;
        }
        Self { p, h, base, modv }
    }

    fn query(&self, l: usize, r: usize) -> i64 {
        let mut res = self.h[r] - self.h[l - 1] * self.p[r - l + 1] % self.modv;
        if res < 0 {
            res += self.modv;
        }
        res % self.modv
    }
}

impl Solution {
    pub fn min_valid_strings(words: Vec<String>, target: String) -> i32 {
        let base = 13331;
        let modv = 998_244_353;
        let hashing = Hashing::new(&target, base, modv);
        let m = words.iter().map(|w| w.len()).max().unwrap_or(0);
        let mut s: Vec<HashSet<i64>> = vec![HashSet::new(); m + 1];

        for w in &words {
            let mut h = 0i64;
            for (j, &b) in w.as_bytes().iter().enumerate() {
                h = (h * base + b as i64) % modv;
                s[j + 1].insert(h);
            }
        }

        let n = target.len();
        let bytes = target.as_bytes();
        let mut ans = 0;
        let mut last = 0;
        let mut mx = 0;

        let f = |i: usize, n: usize, m: usize, s: &Vec<HashSet<i64>>, hashing: &Hashing| -> usize {
            let mut l = 0;
            let mut r = std::cmp::min(n - i, m);
            while l < r {
                let mid = (l + r + 1) >> 1;
                let sub = hashing.query(i + 1, i + mid);
                if s[mid].contains(&sub) {
                    l = mid;
                } else {
                    r = mid - 1;
                }
            }
            l
        };

        for i in 0..n {
            let dist = f(i, n, m, &s, &hashing);
            mx = max(mx, i + dist);
            if i == last {
                if i == mx {
                    return -1;
                }
                last = mx;
                ans += 1;
            }
        }

        ans
    }
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Dynamic Programming Approach

Time Complexity: O(n2) where n is the length of the target string.
Space Complexity: O(n) for the DP array.

Greedy Approach with Backtracking

Time Complexity: Exponential in the worst case, depending on branching factors.
Space Complexity: O(m) where m is the recursion stack depth.

String Hashing + Binary Search + Greedy–

Complexity analysis

ApproachTimeSpaceWhen to Use
Dynamic Programming with Rolling HashO(n log n)O(n)Best general solution for large targets where substring comparisons must be fast
Greedy with BacktrackingO(n^2) worst caseO(n)Useful when candidate matches are small or when implementing a quick recursive solution

Frequently asked questions

Is Minimum Number of Valid Strings to Form Target II easy or hard?

Minimum Number of Valid Strings to Form Target II is classified as a Hard problem. It combines multiple advanced concepts including dynamic programming, rolling hash, substring matching, and optimization of segmentation problems under strict constraints.

Minimum Number of Valid Strings to Form Target II Python/Java solution

Python, JavaScript, and C++ implementations typically use dynamic programming with rolling hash for substring validation. Each step updates dp based on matching valid string lengths. The approach keeps substring checks O(1) and ensures the minimum number of segments is computed efficiently.

How to solve Minimum Number of Valid Strings to Form Target II in O(n log n)?

Compute prefix hashes for the target string and store hashes of valid strings grouped by length. Use dynamic programming where dp[i] represents the minimum segments needed to reach position i. For each index, check candidate lengths using hash comparison and update dp transitions. Binary search over candidate lengths keeps the number of checks small.

What is the best approach for Minimum Number of Valid Strings to Form Target II?

The most efficient approach uses dynamic programming combined with rolling hash or fast substring matching. Define dp[i] as the minimum number of strings needed to form the prefix ending at index i. Rolling hash allows constant-time substring comparisons, reducing overall complexity to about O(n log n) depending on implementation.

Is Minimum Number of Valid Strings to Form Target II asked at Google/Amazon/Meta?

Problems involving string segmentation, rolling hash, and dynamic programming frequently appear in interviews at companies like Google, Amazon, and Meta. Variants of this problem test understanding of substring matching optimization and DP state transitions.

What data structure is used in Minimum Number of Valid Strings to Form Target II?

The main structures include a dynamic programming array, hash tables for storing precomputed string hashes, and sometimes prefix hash arrays for the target string. Some implementations also use segment trees or binary search structures to accelerate substring matching.

What is the time complexity of Minimum Number of Valid Strings to Form Target II?

The optimized dynamic programming solution runs in roughly O(n log n) time with O(n) space when rolling hash and efficient substring lookups are used. Simpler recursive or greedy approaches with backtracking may degrade to O(n^2) in worst cases due to repeated substring checks.

Comments

Now write it yourselfClose this page and solve Minimum Number of Valid Strings to Form Target II in the editor, without looking.
Practise Minimum Number of Valid Strings to Form Target II