Add Bold Tag in String solution and explanation
6 more videos on this problem
LeetCode 616. Add Bold Tag in StringHappy Coding · 14:51 · 4K views
ADD BOLD TAG IN STRING | LEETCODE 616 | PYTHON SOLUTIONCracking FAANG · 13:19 · 3K views
Facebook Coding Interview Question | Leetcode 616 | Add Bold Tag in StringWorkWithGoogler · 21:48 · 2K views
LeetCode 616. Add Bold Tag in String | JSer - JavaScript & AlgorithmJSer · 20:30 · 1K views
616 Add Bold Tag in StringJeremy Huang · 14:30 · 239 views
616. Add Bold Tag in StringJust Coding, with Jie · 17:22 · 88 views
LeetCode 616: Add Bold Tag in StringAlitaCode · 28:17 · 3 views
The problem
You are given a string s and an array of strings words.
You should add a closed pair of bold tag <b> and </b> to wrap the substrings in s that exist in words.
- If two such substrings overlap, you should wrap them together with only one pair of closed bold-tag.
- If two substrings wrapped by bold tags are consecutive, you should combine them.
Return s after adding the bold tags.
Example 1:
Input: s = "abcxyz123", words = ["abc","123"] Output: "<b>abc</b>xyz<b>123</b>" Explanation: The two strings of words are substrings of s as following: "abcxyz123". We add <b> before each substring and </b> after each substring.
Example 2:
Input: s = "aaabbb", words = ["aa","b"] Output: "<b>aaabbb</b>" Explanation: "aa" appears as a substring two times: "aaabbb" and "aaabbb". "b" appears as a substring three times: "aaabbb", "aaabbb", and "aaabbb". We add <b> before each substring and </b> after each substring: "<b>a<b>a</b>a</b><b>b</b><b>b</b><b>b</b>". Since the first two <b>'s overlap, we merge them: "<b>aaa</b><b>b</b><b>b</b><b>b</b>". Since now the four <b>'s are consecutive, we merge them: "<b>aaabbb</b>".
Constraints:
1 <= s.length <= 10000 <= words.length <= 1001 <= words[i].length <= 1000sandwords[i]consist of English letters and digits.- All the values of
wordsare unique.
Note: This question is the same as 758. Bold Words in String.
Approach overview
Problem Overview: Given a string s and a list of dictionary words, wrap every substring in s that appears in the dictionary with <b> and </b>. Overlapping or adjacent matches must be merged into a single bold region.
Approach 1: Brute Force Matching + Interval Merge (O(n * k * L) time, O(n) space)
Scan the string from every index and check whether any dictionary word matches starting at that position using substring comparison. For each match, mark the covered indices in a boolean array or store intervals like [start, end]. After processing all matches, merge overlapping or adjacent intervals and construct the final string by inserting <b> and </b> around bold segments. This approach is straightforward and easy to implement, but repeated substring comparisons make it slower when the dictionary is large.
Approach 2: Boolean Marking with Longest Match Tracking (O(n * k * L) time, O(n) space)
Instead of storing intervals, maintain a boolean array bold[i] indicating whether character i should be bold. While iterating through s, check every dictionary word with a prefix comparison like s.startswith(word, i). Track the farthest index that should remain bold and mark characters up to that boundary. Finally, build the output string while opening a <b> tag when entering a bold region and closing it when leaving one. This method avoids explicit interval merging and is often the cleanest implementation.
Approach 3: Trie-Based String Matching (O(n * L) time, O(W * L) space)
Build a Trie from the dictionary words. Starting at each index in s, traverse the Trie while characters continue to match. Whenever a word end is reached, update the farthest bold boundary. This reduces redundant comparisons because common prefixes are shared in the Trie. The technique is common in string matching problems and works well when the dictionary contains many overlapping prefixes. Use an array or interval list to mark bold positions and generate the final string.
Recommended for interviews: The boolean marking approach is usually expected. It demonstrates clear reasoning about substring matching and interval merging while keeping the implementation simple. Mentioning a Trie optimization shows deeper understanding of scalable string matching techniques.
Solution
class Trie:
def __init__(self):
self.children = [None] * 128
self.is_end = False
def insert(self, word):
node = self
for c in word:
idx = ord(c)
if node.children[idx] is None:
node.children[idx] = Trie()
node = node.children[idx]
node.is_end = True
class Solution:
def addBoldTag(self, s: str, words: List[str]) -> str:
trie = Trie()
for w in words:
trie.insert(w)
n = len(s)
pairs = []
for i in range(n):
node = trie
for j in range(i, n):
idx = ord(s[j])
if node.children[idx] is None:
break
node = node.children[idx]
if node.is_end:
pairs.append([i, j])
if not pairs:
return s
st, ed = pairs[0]
t = []
for a, b in pairs[1:]:
if ed + 1 < a:
t.append([st, ed])
st, ed = a, b
else:
ed = max(ed, b)
t.append([st, ed])
ans = []
i = j = 0
while i < n:
if j == len(t):
ans.append(s[i:])
break
st, ed = t[j]
if i < st:
ans.append(s[i:st])
ans.append('<b>')
ans.append(s[st : ed + 1])
ans.append('</b>')
j += 1
i = ed + 1
return ''.join(ans)class Trie {
Trie[] children = new Trie[128];
boolean isEnd;
public void insert(String word) {
Trie node = this;
for (char c : word.toCharArray()) {
if (node.children[c] == null) {
node.children[c] = new Trie();
}
node = node.children[c];
}
node.isEnd = true;
}
}
class Solution {
public String addBoldTag(String s, String[] words) {
Trie trie = new Trie();
for (String w : words) {
trie.insert(w);
}
List<int[]> pairs = new ArrayList<>();
int n = s.length();
for (int i = 0; i < n; ++i) {
Trie node = trie;
for (int j = i; j < n; ++j) {
int idx = s.charAt(j);
if (node.children[idx] == null) {
break;
}
node = node.children[idx];
if (node.isEnd) {
pairs.add(new int[] {i, j});
}
}
}
if (pairs.isEmpty()) {
return s;
}
List<int[]> t = new ArrayList<>();
int st = pairs.get(0)[0], ed = pairs.get(0)[1];
for (int j = 1; j < pairs.size(); ++j) {
int a = pairs.get(j)[0], b = pairs.get(j)[1];
if (ed + 1 < a) {
t.add(new int[] {st, ed});
st = a;
ed = b;
} else {
ed = Math.max(ed, b);
}
}
t.add(new int[] {st, ed});
int i = 0, j = 0;
StringBuilder ans = new StringBuilder();
while (i < n) {
if (j == t.size()) {
ans.append(s.substring(i));
break;
}
st = t.get(j)[0];
ed = t.get(j)[1];
if (i < st) {
ans.append(s.substring(i, st));
}
++j;
ans.append("<b>");
ans.append(s.substring(st, ed + 1));
ans.append("</b>");
i = ed + 1;
}
return ans.toString();
}
}class Trie {
public:
vector<Trie*> children;
bool isEnd;
Trie() {
children.resize(128);
isEnd = false;
}
void insert(string word) {
Trie* node = this;
for (char c : word) {
if (!node->children[c]) node->children[c] = new Trie();
node = node->children[c];
}
node->isEnd = true;
}
};
class Solution {
public:
string addBoldTag(string s, vector<string>& words) {
Trie* trie = new Trie();
for (string w : words) trie->insert(w);
int n = s.size();
vector<pair<int, int>> pairs;
for (int i = 0; i < n; ++i) {
Trie* node = trie;
for (int j = i; j < n; ++j) {
int idx = s[j];
if (!node->children[idx]) break;
node = node->children[idx];
if (node->isEnd) pairs.push_back({i, j});
}
}
if (pairs.empty()) return s;
vector<pair<int, int>> t;
int st = pairs[0].first, ed = pairs[0].second;
for (int i = 1; i < pairs.size(); ++i) {
int a = pairs[i].first, b = pairs[i].second;
if (ed + 1 < a) {
t.push_back({st, ed});
st = a, ed = b;
} else
ed = max(ed, b);
}
t.push_back({st, ed});
string ans = "";
int i = 0, j = 0;
while (i < n) {
if (j == t.size()) {
ans += s.substr(i);
break;
}
st = t[j].first, ed = t[j].second;
if (i < st) ans += s.substr(i, st - i);
ans += "<b>";
ans += s.substr(st, ed - st + 1);
ans += "</b>";
i = ed + 1;
++j;
}
return ans;
}
};type Trie struct {
children [128]*Trie
isEnd bool
}
func newTrie() *Trie {
return &Trie{}
}
func (this *Trie) insert(word string) {
node := this
for _, c := range word {
if node.children[c] == nil {
node.children[c] = newTrie()
}
node = node.children[c]
}
node.isEnd = true
}
func addBoldTag(s string, words []string) string {
trie := newTrie()
for _, w := range words {
trie.insert(w)
}
n := len(s)
var pairs [][]int
for i := range s {
node := trie
for j := i; j < n; j++ {
if node.children[s[j]] == nil {
break
}
node = node.children[s[j]]
if node.isEnd {
pairs = append(pairs, []int{i, j})
}
}
}
if len(pairs) == 0 {
return s
}
var t [][]int
st, ed := pairs[0][0], pairs[0][1]
for i := 1; i < len(pairs); i++ {
a, b := pairs[i][0], pairs[i][1]
if ed+1 < a {
t = append(t, []int{st, ed})
st, ed = a, b
} else {
ed = max(ed, b)
}
}
t = append(t, []int{st, ed})
var ans strings.Builder
i, j := 0, 0
for i < n {
if j == len(t) {
ans.WriteString(s[i:])
break
}
st, ed = t[j][0], t[j][1]
if i < st {
ans.WriteString(s[i:st])
}
ans.WriteString("<b>")
ans.WriteString(s[st : ed+1])
ans.WriteString("</b>")
i = ed + 1
j++
}
return ans.String()
}Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Brute Force Matching + Interval Merge | O(n * k * L) | O(n) | Good for understanding the problem and when dictionary size is small |
| Boolean Marking with Longest Match | O(n * k * L) | O(n) | Clean implementation for interviews; avoids explicit interval merging |
| Trie-Based String Matching | O(n * L) | O(W * L) | Best when dictionary has many words with shared prefixes |
Frequently asked questions
Is Add Bold Tag in String easy or hard?
Add Bold Tag in String is rated Medium difficulty. The main challenge is handling overlapping and adjacent matches correctly while constructing the final string. Once you recognize that all matches can be converted into merged intervals or a boolean marking array, the implementation becomes straightforward.
Add Bold Tag in String Python/Java solution
In Python or Java, iterate through the string and check each dictionary word using prefix matching such as startswith or substring comparison. Track the farthest bold boundary and mark characters in a boolean array. Finally, build the result by inserting <b> and </b> tags when transitioning between non-bold and bold regions.
How to solve Add Bold Tag in String in O(n)?
Near-linear performance can be achieved by building a Trie from all dictionary words and scanning the string while traversing the Trie for matching prefixes. Each character is processed while following possible matches up to the maximum word length L. This reduces redundant comparisons and results in O(n * L) time with O(W * L) space for the Trie structure.
What is the best approach for Add Bold Tag in String?
The boolean marking approach is typically the best balance of simplicity and efficiency. Iterate through the string, check if any dictionary word starts at each index, and track the farthest position that should remain bold. Mark characters in a boolean array and build the final string by opening and closing <b> tags when entering or leaving bold regions. Time complexity is O(n * k * L) where n is the string length, k is the number of words, and L is the maximum word length.
Is Add Bold Tag in String asked at Google/Amazon/Meta?
Add Bold Tag in String has appeared in interviews at companies that emphasize string manipulation and text processing, including Google and Facebook-style interview loops. The problem tests substring matching, interval merging, and careful string construction—skills frequently evaluated in mid-level algorithm interviews.
What data structure is used in Add Bold Tag in String?
Most solutions use arrays or interval lists to track which characters should be bold. A boolean array of length n is common for marking bold regions. For optimization, a Trie data structure can store dictionary words and enable efficient prefix-based string matching.
What is the time complexity of Add Bold Tag in String?
The common solution runs in O(n * k * L) time because for each position in the string you may check up to k words with length up to L. Space complexity is O(n) for the boolean array used to mark bold characters. A Trie-based optimization can reduce repeated prefix comparisons and achieve roughly O(n * L) matching time.
