@@ -88,67 +88,117 @@ class AhoCorasick {
88
88
}
89
89
}
90
90
91
+ // another
91
92
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 ( ) )
92
103
93
-
94
- // TLE below
95
-
96
- const mx = 1000000000
97
104
/**
98
105
* @param {string } target
99
106
* @param {string[] } words
100
107
* @param {number[] } costs
101
108
* @return {number }
102
109
*/
103
- var minimumCost = function ( target , words , costs ) {
110
+ function minimumCost ( target , words , costs ) {
111
+ const trie = new Trie ( words , costs )
104
112
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
107
115
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
+ }
110
121
}
111
122
112
- dp [ n ] = 0
123
+ return dp [ n ] === Infinity ? - 1 : dp [ n ]
124
+ }
113
125
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 ]
119
141
}
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
122
144
}
123
145
}
124
- }
125
146
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
+ }
128
161
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
+ }
135
167
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 )
141
170
}
142
- cur = cur . sons [ c . charCodeAt ( 0 ) - 'a' . charCodeAt ( 0 ) ]
143
171
}
144
- cur . cost = Math . min ( cur . cost , cost )
172
+ this . curr = this . root
145
173
}
146
174
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
151
200
}
152
201
}
202
+ return result
153
203
}
154
204
}
0 commit comments