Skip to content

Commit 36d3b43

Browse files
authored
Update 3213-construct-string-with-minimum-cost.js
1 parent f4d3a0f commit 36d3b43

File tree

1 file changed

+87
-37
lines changed

1 file changed

+87
-37
lines changed

3213-construct-string-with-minimum-cost.js

Lines changed: 87 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -88,67 +88,117 @@ class AhoCorasick {
8888
}
8989
}
9090

91+
// another
9192

93+
class TrieNode {
94+
constructor() {
95+
this.sfx = null // Suffix link
96+
this.dict = null // Dictionary link
97+
this.child = new Array(26).fill(null)
98+
this.word_id = -1 // Index of the word ending at this node
99+
}
100+
}
101+
// Speeds up Trie construction
102+
const preallocated_nodes = Array.from({ length: 50005 }, () => new TrieNode())
92103

93-
94-
// TLE below
95-
96-
const mx = 1000000000
97104
/**
98105
* @param {string} target
99106
* @param {string[]} words
100107
* @param {number[]} costs
101108
* @return {number}
102109
*/
103-
var minimumCost = function (target, words, costs) {
110+
function minimumCost(target, words, costs) {
111+
const trie = new Trie(words, costs)
104112
const n = target.length
105-
const dp = new Array(n + 1).fill(mx)
106-
const t = new Trie(30)
113+
const dp = new Array(n + 1).fill(Infinity)
114+
dp[0] = 0
107115

108-
for (let i = 0; i < words.length; i++) {
109-
t.insert(words[i], costs[i])
116+
for (let i = 1; i <= n; ++i) {
117+
const suffixes = trie.suffixesAfterAppending(target[i - 1])
118+
for (const j of suffixes) {
119+
dp[i] = Math.min(dp[i], dp[i - words[j].length] + costs[j])
120+
}
110121
}
111122

112-
dp[n] = 0
123+
return dp[n] === Infinity ? -1 : dp[n]
124+
}
113125

114-
for (let i = n - 1; i >= 0; i--) {
115-
let cur = t
116-
for (let j = i; j <= n && cur !== null; j++) {
117-
if (cur.cost !== mx) {
118-
dp[i] = Math.min(dp[j] + cur.cost, dp[i])
126+
class Trie {
127+
constructor(words, costs) {
128+
this.count = 0
129+
this.root = this.newTrieNode()
130+
this.root.sfx = this.root.dict = this.root
131+
132+
for (let i = 0; i < words.length; ++i) {
133+
const word = words[i]
134+
let u = this.root
135+
for (const c of word) {
136+
const index = c.charCodeAt(0) - 'a'.charCodeAt(0)
137+
if (!u.child[index]) {
138+
u.child[index] = this.newTrieNode()
139+
}
140+
u = u.child[index]
119141
}
120-
if (j < n) {
121-
cur = cur.sons[target.charCodeAt(j) - 'a'.charCodeAt(0)]
142+
if (u.word_id < 0 || costs[i] < costs[u.word_id]) {
143+
u.word_id = i
122144
}
123145
}
124-
}
125146

126-
return dp[0] === mx ? -1 : dp[0]
127-
}
147+
// BFS is used to set up the suffix and dictionary links for each node
148+
// The suffix link of a node points to the longest proper suffix of the word represented by the node that is also a prefix of some word in the dictionary.
149+
// The dictionary link is used to quickly find the next node in the dictionary chain.
150+
const queue = [this.root]
151+
while (queue.length > 0) {
152+
const u = queue.shift()
153+
for (let i = 0; i < 26; ++i) {
154+
const v = u.child[i]
155+
if (!v) continue
156+
157+
let p = u.sfx
158+
while (p !== this.root && !p.child[i]) {
159+
p = p.sfx
160+
}
128161

129-
class Trie {
130-
constructor(range) {
131-
this.range = range
132-
this.cost = mx
133-
this.sons = new Array(range).fill(null)
134-
}
162+
if (u !== this.root && p.child[i]) {
163+
v.sfx = p.child[i]
164+
} else {
165+
v.sfx = this.root
166+
}
135167

136-
insert(str, cost) {
137-
let cur = this
138-
for (let c of str) {
139-
if (cur.sons[c.charCodeAt(0) - 'a'.charCodeAt(0)] === null) {
140-
cur.sons[c.charCodeAt(0) - 'a'.charCodeAt(0)] = new Trie(this.range)
168+
v.dict = v.sfx.word_id >= 0 ? v.sfx : v.sfx.dict
169+
queue.push(v)
141170
}
142-
cur = cur.sons[c.charCodeAt(0) - 'a'.charCodeAt(0)]
143171
}
144-
cur.cost = Math.min(cur.cost, cost)
172+
this.curr = this.root
145173
}
146174

147-
destroy() {
148-
for (let t of this.sons) {
149-
if (t !== null) {
150-
t.destroy()
175+
newTrieNode() {
176+
preallocated_nodes[this.count] = new TrieNode()
177+
return preallocated_nodes[this.count++]
178+
}
179+
180+
// This method is used to update the current node and find all matching suffixes after appending a character
181+
suffixesAfterAppending(letter) {
182+
const index = letter.charCodeAt(0) - 'a'.charCodeAt(0)
183+
184+
// It follows suffix links until it finds a child node corresponding to the character or reaches the root.
185+
while (this.curr !== this.root && !this.curr.child[index]) {
186+
this.curr = this.curr.sfx
187+
}
188+
189+
// If a valid child node is found, it updates the current node and collects all word IDs reachable through the dictionary links.
190+
const result = []
191+
if (this.curr.child[index]) {
192+
this.curr = this.curr.child[index]
193+
let u = this.curr
194+
if (u.word_id < 0) {
195+
u = u.dict
196+
}
197+
while (u.word_id >= 0) {
198+
result.push(u.word_id)
199+
u = u.dict
151200
}
152201
}
202+
return result
153203
}
154204
}

0 commit comments

Comments
 (0)