You are given a list of strings words
and a string s
. Your task is to wrap substrings in s
that match any word in words
with bold tags <b>
and </b>
. If two bolded substrings overlap or are consecutive, you should merge them into a single bolded substring.
For example, if words = ["ab", "bc"]
and s = "aabcd"
, then both "aabcd" and "aabcd" are possible, but you must ensure all possible substrings are merged and bolded together if overlapping or adjacent.
words
can be used multiple times and may overlap.s
consists of lowercase English letters.<b>
tags such that all substrings matching any word in words
are bolded.
The naive approach is to check every substring of s
to see if it matches any word in words
. For each match, mark the corresponding substring as bold. However, this would be inefficient for large s
or many words.
To optimize, we realize that we need to efficiently find all occurrences of each word in words
within s
. Once we know which intervals of s
should be bolded, we can merge overlapping or adjacent intervals and insert the bold tags.
The main challenge is:
s
that should be bolded.We can solve the problem in the following steps:
bold
of the same length as s
, initialized to False
.words
, iterate through s
and mark positions that are part of a match as True
in bold
.bold
array naturally represents merged intervals.s
and use the bold
array to determine where to insert <b>
and </b>
tags.Why a boolean array? Using a boolean array allows us to efficiently mark and merge all intervals where a word is found, without explicitly storing start and end indices for each interval.
Alternative: For very large words
lists, a Trie could speed up the matching step, but for most constraints, the above approach is sufficient.
Suppose words = ["ab", "bc"]
and s = "aabcd"
.
bold[1]
and bold[2]
as True
.bold[2]
and bold[3]
as True
.bold
array: [False, True, True, True, False]<b>
before 'a'</b>
before 'd'a<b>abc</b>d
This example shows how overlapping matches ("ab" and "bc") are merged into a single bold region.
Brute-force approach:
s
against every word is O(N^2 * K), where N is the length of s
and K is the number of words.s
in O(N) time, so total time is O(N * W * L), where W is the number of words and L is the average word length.bold
array and the output string.
If a Trie is used for matching, the preprocessing is O(total characters in words
), and searching is O(N * max_word_length).
The key insight is to efficiently mark all regions of s
that should be bolded, using a boolean array to merge overlapping and adjacent intervals. By scanning through s
and marking matches, then constructing the output with minimal bold tags, we achieve a clean and optimal solution. This approach is both intuitive and efficient for typical input sizes.
class Solution:
def boldWords(self, words, s):
n = len(s)
bold = [False] * n
for word in words:
start = s.find(word)
while start != -1:
for i in range(start, start + len(word)):
bold[i] = True
start = s.find(word, start + 1)
res = []
i = 0
while i < n:
if not bold[i]:
res.append(s[i])
i += 1
else:
res.append('<b>')
while i < n and bold[i]:
res.append(s[i])
i += 1
res.append('</b>')
return ''.join(res)
class Solution {
public:
string boldWords(vector<string>& words, string s) {
int n = s.size();
vector<bool> bold(n, false);
for (const string& word : words) {
size_t pos = s.find(word);
while (pos != string::npos) {
for (int i = pos; i < pos + word.size(); ++i)
bold[i] = true;
pos = s.find(word, pos + 1);
}
}
string res;
int i = 0;
while (i < n) {
if (!bold[i]) {
res += s[i++];
} else {
res += "<b>";
while (i < n && bold[i]) {
res += s[i++];
}
res += "</b>";
}
}
return res;
}
};
class Solution {
public String boldWords(String[] words, String s) {
int n = s.length();
boolean[] bold = new boolean[n];
for (String word : words) {
int start = s.indexOf(word);
while (start != -1) {
for (int i = start; i < start + word.length(); ++i)
bold[i] = true;
start = s.indexOf(word, start + 1);
}
}
StringBuilder res = new StringBuilder();
int i = 0;
while (i < n) {
if (!bold[i]) {
res.append(s.charAt(i++));
} else {
res.append("<b>");
while (i < n && bold[i]) {
res.append(s.charAt(i++));
}
res.append("</b>");
}
}
return res.toString();
}
}
var boldWords = function(words, s) {
const n = s.length;
const bold = new Array(n).fill(false);
for (const word of words) {
let start = s.indexOf(word);
while (start !== -1) {
for (let i = start; i < start + word.length; ++i)
bold[i] = true;
start = s.indexOf(word, start + 1);
}
}
let res = '';
let i = 0;
while (i < n) {
if (!bold[i]) {
res += s[i++];
} else {
res += '<b>';
while (i < n && bold[i]) {
res += s[i++];
}
res += '</b>';
}
}
return res;
};