# 3213. Construct String with Minimum Cost

## Description

You are given a string target, an array of strings words, and an integer array costs, both arrays of the same length.

Imagine an empty string s.

You can perform the following operation any number of times (including zero):

• Choose an index i in the range [0, words.length - 1].
• Append words[i] to s.
• The cost of operation is costs[i].

Return the minimum cost to make s equal to target. If it's not possible, return -1.

Example 1:

Input: target = "abcdef", words = ["abdef","abc","d","def","ef"], costs = [100,1,1,10,5]

Output: 7

Explanation:

The minimum cost can be achieved by performing the following operations:

• Select index 1 and append "abc" to s at a cost of 1, resulting in s = "abc".
• Select index 2 and append "d" to s at a cost of 1, resulting in s = "abcd".
• Select index 4 and append "ef" to s at a cost of 5, resulting in s = "abcdef".

Example 2:

Input: target = "aaaa", words = ["z","zz","zzz"], costs = [1,10,100]

Output: -1

Explanation:

It is impossible to make s equal to target, so we return -1.

Constraints:

• 1 <= target.length <= 5 * 104
• 1 <= words.length == costs.length <= 5 * 104
• 1 <= words[i].length <= target.length
• The total sum of words[i].length is less than or equal to 5 * 104.
• target and words[i] consist only of lowercase English letters.
• 1 <= costs[i] <= 104

## Solutions

### Solution 1: String Hashing + Dynamic Programming + Enumerating Length

We define $f[i]$ as the minimum cost to construct the first $i$ characters of $\textit{target}$, with the initial condition $f[0] = 0$ and all other values set to infinity. The answer is $f[n]$, where $n$ is the length of $\textit{target}$.

For the current $f[i]$, consider enumerating the length $j$ of the word. If $j \leq i$, then we can consider the hash value of the segment from $i - j + 1$ to $i$. If this hash value corresponds to an existing word, then we can transition from $f[i - j]$ to $f[i]$. The state transition equation is as follows:

$f[i] = \min(f[i], f[i - j] + \textit{cost}[k])$

where $\textit{cost}[k]$ represents the minimum cost of a word of length $j$ whose hash value matches $\textit{target}[i - j + 1, i]$.

The time complexity is $O(n \times \sqrt{L})$, and the space complexity is $O(n)$. Here, $n$ is the length of $\textit{target}$, and $L$ is the sum of the lengths of all words in the array $\textit{words}$.

• 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 {
public int minimumCost(String target, String[] words, int[] costs) {
final int base = 13331;
final int mod = 998244353;
final int inf = Integer.MAX_VALUE / 2;

int n = target.length();
Hashing hashing = new Hashing(target, base, mod);

int[] f = new int[n + 1];
Arrays.fill(f, inf);
f[0] = 0;

TreeSet<Integer> ss = new TreeSet<>();
for (String w : words) {
}

Map<Long, Integer> d = new HashMap<>();
for (int i = 0; i < words.length; i++) {
long x = 0;
for (char c : words[i].toCharArray()) {
x = (x * base + c) % mod;
}
d.merge(x, costs[i], Integer::min);
}

for (int i = 1; i <= n; i++) {
for (int j : ss) {
if (j > i) {
break;
}
long x = hashing.query(i - j + 1, i);
f[i] = Math.min(f[i], f[i - j] + d.getOrDefault(x, inf));
}
}

return f[n] >= inf ? -1 : f[n];
}
}

• class Hashing {
private:
vector<long> p, h;
long mod;

public:
Hashing(const string& word, long base, long mod)
: p(word.size() + 1, 1)
, h(word.size() + 1, 0)
, mod(mod) {
for (int i = 1; i <= word.size(); ++i) {
p[i] = p[i - 1] * base % mod;
h[i] = (h[i - 1] * base + word[i - 1]) % mod;
}
}

long query(int l, int r) {
return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod;
}
};

class Solution {
public:
int minimumCost(string target, vector<string>& words, vector<int>& costs) {
const int base = 13331;
const int mod = 998244353;
const int inf = INT_MAX / 2;

int n = target.size();
Hashing hashing(target, base, mod);

vector<int> f(n + 1, inf);
f[0] = 0;

set<int> ss;
for (const string& w : words) {
ss.insert(w.size());
}

unordered_map<long, int> d;
for (int i = 0; i < words.size(); ++i) {
long x = 0;
for (char c : words[i]) {
x = (x * base + c) % mod;
}
d[x] = d.find(x) == d.end() ? costs[i] : min(d[x], costs[i]);
}

for (int i = 1; i <= n; ++i) {
for (int j : ss) {
if (j > i) {
break;
}
long x = hashing.query(i - j + 1, i);
if (d.contains(x)) {
f[i] = min(f[i], f[i - j] + d[x]);
}
}
}

return f[n] >= inf ? -1 : f[n];
}
};

• class Solution:
def minimumCost(self, target: str, words: List[str], costs: List[int]) -> int:
base, mod = 13331, 998244353
n = len(target)
h = [0] * (n + 1)
p = [1] * (n + 1)
for i, c in enumerate(target, 1):
h[i] = (h[i - 1] * base + ord(c)) % mod
p[i] = (p[i - 1] * base) % mod
f = [0] + [inf] * n
ss = sorted(set(map(len, words)))
d = defaultdict(lambda: inf)
min = lambda a, b: a if a < b else b
for w, c in zip(words, costs):
x = 0
for ch in w:
x = (x * base + ord(ch)) % mod
d[x] = min(d[x], c)
for i in range(1, n + 1):
for j in ss:
if j > i:
break
x = (h[i] - h[i - j] * p[j]) % mod
f[i] = min(f[i], f[i - j] + d[x])
return f[n] if f[n] < inf else -1

• type Hashing struct {
p   []int64
h   []int64
mod int64
}

func NewHashing(word string, base, 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 (hs *Hashing) query(l, r int) int64 {
return (hs.h[r] - hs.h[l-1]*hs.p[r-l+1]%hs.mod + hs.mod) % hs.mod
}

func minimumCost(target string, words []string, costs []int) int {
const base = 13331
const mod = 998244353
const inf = math.MaxInt32 / 2

n := len(target)
hashing := NewHashing(target, base, mod)

f := make([]int, n+1)
for i := range f {
f[i] = inf
}
f[0] = 0

ss := make(map[int]struct{})
for _, w := range words {
ss[len(w)] = struct{}{}
}
lengths := make([]int, 0, len(ss))
for length := range ss {
lengths = append(lengths, length)
}
sort.Ints(lengths)

d := make(map[int64]int)
for i, w := range words {
var x int64
for _, c := range w {
x = (x*base + int64(c)) % mod
}
if existingCost, exists := d[x]; exists {
if costs[i] < existingCost {
d[x] = costs[i]
}
} else {
d[x] = costs[i]
}
}

for i := 1; i <= n; i++ {
for _, j := range lengths {
if j > i {
break
}
x := hashing.query(i-j+1, i)
if cost, ok := d[x]; ok {
f[i] = min(f[i], f[i-j]+cost)
}
}
}

if f[n] >= inf {
return -1
}
return f[n]
}