616. Add Bold Tag in String π
Problem Description
You are given a string s
and an array of strings words
. Your task is to add HTML bold tags (<b>
and </b>
) around substrings in s
that match any word in the words
array.
The key rules for adding bold tags are:
-
Find all matching substrings: Any substring of
s
that exactly matches a word fromwords
should be wrapped with bold tags. -
Merge overlapping regions: If two substrings that need to be bolded overlap (share at least one character), they should be wrapped together with a single pair of bold tags instead of having nested or overlapping tags.
-
Merge consecutive regions: If two bold regions are adjacent (the end of one bold region is immediately followed by the start of another), they should be combined into one continuous bold region.
For example:
- If
s = "abcxyz"
andwords = ["abc", "xyz"]
, the result would be"<b>abcxyz</b>"
because the two words are consecutive. - If
s = "aaabbcc"
andwords = ["aaa", "aab", "bc"]
, the result would be"<b>aaabbc</b>c"
because "aaa" and "aab" overlap (sharing "aa"), and "aab" and "bc" overlap (sharing "b"), creating one continuous bold region.
The solution uses a Trie data structure to efficiently find all matching substrings in s
. It first identifies all intervals that need to be bolded, then merges overlapping or consecutive intervals, and finally constructs the result string with the appropriate bold tags inserted.
Intuition
The core challenge is efficiently finding all occurrences of words from the words
array within the string s
, then merging overlapping or adjacent regions.
A naive approach would be to check each word against every position in s
, but this becomes inefficient when we have many words or long strings. Instead, we can think of this problem in reverse: for each position in s
, we want to find all words that start at that position.
This naturally leads us to use a Trie (prefix tree) structure. Why? Because a Trie allows us to simultaneously check for multiple words while traversing through the string character by character. As we move through s
starting from each position, we can traverse the Trie and identify all valid words that begin at that position.
The process breaks down into three logical steps:
-
Build a Trie from all words to enable efficient multi-pattern matching.
-
Find all intervals that need to be bolded. For each starting position
i
ins
, we traverse the Trie to find all words that match substrings starting ati
. Each match gives us an interval[start, end]
that needs bold tags. -
Merge intervals that overlap or are consecutive. This is a classic interval merging problem - if the end of one interval touches or overlaps with the start of the next interval (i.e.,
end + 1 >= next_start
), we merge them into a single interval. This ensures we don't have nested tags or immediately adjacent bold regions.
The beauty of this approach is that the Trie allows us to find all matching words in a single pass from each position, making the algorithm efficient even with many words to search for. The interval merging step then cleanly handles the requirement to combine overlapping and adjacent bold regions.
Learn more about Trie patterns.
Solution Approach
The implementation consists of three main components: building a Trie, finding all matching intervals, and merging those intervals.
1. Building the Trie Structure
The Trie class is implemented with:
children
: An array of size 128 to accommodate all ASCII characters (usingord(c)
as the index)is_end
: A boolean flag marking if a word ends at this node
def insert(self, word):
node = self
for c in word:
idx = ord(c)
if node.children[idx] is None:
node.children[idx] = [Trie](/problems/trie_intro)()
node = node.children[idx]
node.is_end = True
All words from the words
array are inserted into the Trie for efficient pattern matching.
2. Finding All Matching Intervals
For each position i
in string s
, we traverse the Trie to find all words that start at position i
:
for i in range(n):
node = [trie](/problems/trie_intro)
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])
When we find a complete word (marked by is_end = True
), we record the interval [i, j]
where i
is the start index and j
is the end index of the matching substring.
3. Merging Overlapping and Adjacent Intervals
After collecting all intervals, we merge those that overlap or are consecutive:
st, ed = pairs[0]
t = []
for a, b in pairs[1:]:
if ed + 1 < a: # No overlap or adjacency
t.append([st, ed])
st, ed = a, b
else: # Overlap or adjacent
ed = max(ed, b)
t.append([st, ed])
The merging logic checks if ed + 1 < a
, which means there's a gap between intervals. If true, we save the current interval and start a new one. Otherwise, we extend the current interval by updating ed = max(ed, b)
.
4. Constructing the Final String
Finally, we build the result string by iterating through s
and the merged intervals simultaneously:
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]) # Add non-bold text
ans.append('<b>')
ans.append(s[st : ed + 1]) # Add bold text
ans.append('</b>')
j += 1
i = ed + 1
The algorithm maintains two pointers: i
for the current position in string s
, and j
for the current interval in the merged list. It adds non-bold text between intervals and wraps the intervals with <b>
tags.
The time complexity is O(n * m + k)
where n
is the length of s
, m
is the average length of words, and k
is the total number of matching intervals. The space complexity is O(T + k)
where T
is the size of the Trie.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the solution works step by step.
Given:
s = "aaabbcc"
words = ["aaa", "aab", "bc"]
Step 1: Build the Trie
We insert all words into the Trie structure:
Root βββ a β βββ a β βββ a (is_end = True) // "aaa" β βββ b (is_end = True) // "aab" βββ b βββ c (is_end = True) // "bc"
Step 2: Find All Matching Intervals
Now we scan through each position in s = "aaabbcc"
and find matches:
-
Position 0 (
"aaabbcc"
):- Follow path aβaβa in Trie β Found "aaa" at [0,2]
- Follow path aβaβb in Trie β Found "aab" at [0,3]
-
Position 1 (
"aabbcc"
):- Follow path aβaβb in Trie β Found "aab" at [1,4]
-
Position 2 (
"abbcc"
):- Follow path aβb in Trie β No match (path doesn't exist)
-
Position 3 (
"bbcc"
):- Follow path bβb in Trie β No match (path doesn't exist)
-
Position 4 (
"bcc"
):- Follow path bβc in Trie β Found "bc" at [4,5]
-
Position 5 (
"cc"
):- Follow path c in Trie β No match (no child 'c' from root)
-
Position 6 (
"c"
):- Follow path c in Trie β No match (no child 'c' from root)
Collected intervals: [[0,2], [0,3], [1,4], [4,5]]
Step 3: Merge Overlapping/Adjacent Intervals
Sort intervals by start position (already sorted): [[0,2], [0,3], [1,4], [4,5]]
Merging process:
- Start with [0,2]
- Check [0,3]: Since 2+1 β₯ 0 (overlapping), merge to [0,3]
- Check [1,4]: Since 3+1 β₯ 1 (overlapping), merge to [0,4]
- Check [4,5]: Since 4+1 β₯ 4 (adjacent), merge to [0,5]
Merged intervals: [[0,5]]
Step 4: Construct Final String
With merged interval [0,5] and s = "aaabbcc"
:
- Position 0: Start of interval [0,5]
- Add
<b>
- Add substring from index 0 to 5: "aaabbc"
- Add
</b>
- Add
- Position 6: After interval ends
- Add remaining string: "c"
Final Result: "<b>aaabbc</b>c"
The overlapping matches "aaa" (positions 0-2), "aab" (positions 0-3 and 1-4), and "bc" (positions 4-5) all merge into one continuous bold region because they share characters or are adjacent, resulting in a single pair of bold tags around "aaabbc".
Solution Implementation
1class Trie:
2 def __init__(self):
3 # Initialize children array for ASCII characters (128 possible values)
4 self.children = [None] * 128
5 # Flag to mark if current node represents end of a word
6 self.is_end = False
7
8 def insert(self, word: str) -> None:
9 """Insert a word into the trie"""
10 node = self
11 for char in word:
12 # Get ASCII value of character
13 index = ord(char)
14 # Create new node if path doesn't exist
15 if node.children[index] is None:
16 node.children[index] = Trie()
17 # Move to next node
18 node = node.children[index]
19 # Mark the end of word
20 node.is_end = True
21
22
23class Solution:
24 def addBoldTag(self, s: str, words: List[str]) -> str:
25 """
26 Add bold tags around substrings found in words list.
27 Merges overlapping or adjacent bold regions.
28 """
29 # Build trie from all words
30 trie = Trie()
31 for word in words:
32 trie.insert(word)
33
34 n = len(s)
35 # Store all matching intervals as [start, end] pairs
36 intervals = []
37
38 # Find all matching substrings starting from each position
39 for i in range(n):
40 node = trie
41 for j in range(i, n):
42 index = ord(s[j])
43 # Stop if no matching path in trie
44 if node.children[index] is None:
45 break
46 node = node.children[index]
47 # If we found a complete word, add interval
48 if node.is_end:
49 intervals.append([i, j])
50
51 # Return original string if no matches found
52 if not intervals:
53 return s
54
55 # Merge overlapping or adjacent intervals
56 merged_intervals = []
57 start, end = intervals[0]
58
59 for curr_start, curr_end in intervals[1:]:
60 # If intervals are disjoint (not adjacent or overlapping)
61 if end + 1 < curr_start:
62 merged_intervals.append([start, end])
63 start, end = curr_start, curr_end
64 else:
65 # Extend the current interval
66 end = max(end, curr_end)
67 # Add the last interval
68 merged_intervals.append([start, end])
69
70 # Build result string with bold tags
71 result = []
72 string_index = 0
73 interval_index = 0
74
75 while string_index < n:
76 # If no more intervals, append remaining string
77 if interval_index == len(merged_intervals):
78 result.append(s[string_index:])
79 break
80
81 interval_start, interval_end = merged_intervals[interval_index]
82
83 # Append non-bold portion before current interval
84 if string_index < interval_start:
85 result.append(s[string_index:interval_start])
86
87 # Append bold portion with tags
88 result.append('<b>')
89 result.append(s[interval_start:interval_end + 1])
90 result.append('</b>')
91
92 # Move to next interval and update string position
93 interval_index += 1
94 string_index = interval_end + 1
95
96 return ''.join(result)
97
1class Trie {
2 // Array to store child nodes for all ASCII characters
3 Trie[] children = new Trie[128];
4 // Flag to mark end of a word
5 boolean isEnd;
6
7 /**
8 * Inserts a word into the trie
9 * @param word the word to insert
10 */
11 public void insert(String word) {
12 Trie node = this;
13 // Traverse each character in the word
14 for (char c : word.toCharArray()) {
15 // Create new node if path doesn't exist
16 if (node.children[c] == null) {
17 node.children[c] = new Trie();
18 }
19 // Move to the child node
20 node = node.children[c];
21 }
22 // Mark the end of the word
23 node.isEnd = true;
24 }
25}
26
27class Solution {
28 /**
29 * Adds bold tags around substrings that exist in the words array
30 * @param s the input string
31 * @param words array of words to be bolded
32 * @return string with bold tags added
33 */
34 public String addBoldTag(String s, String[] words) {
35 // Build trie with all words
36 Trie trie = new Trie();
37 for (String word : words) {
38 trie.insert(word);
39 }
40
41 // Find all matching intervals [start, end]
42 List<int[]> intervals = new ArrayList<>();
43 int n = s.length();
44
45 // Check each position as potential start of a word
46 for (int i = 0; i < n; ++i) {
47 Trie node = trie;
48 // Try to match words starting at position i
49 for (int j = i; j < n; ++j) {
50 int charIndex = s.charAt(j);
51 // No match found, break
52 if (node.children[charIndex] == null) {
53 break;
54 }
55 // Move to next node
56 node = node.children[charIndex];
57 // If we found a complete word, record the interval
58 if (node.isEnd) {
59 intervals.add(new int[] {i, j});
60 }
61 }
62 }
63
64 // No words found, return original string
65 if (intervals.isEmpty()) {
66 return s;
67 }
68
69 // Merge overlapping or adjacent intervals
70 List<int[]> mergedIntervals = new ArrayList<>();
71 int start = intervals.get(0)[0];
72 int end = intervals.get(0)[1];
73
74 for (int j = 1; j < intervals.size(); ++j) {
75 int currentStart = intervals.get(j)[0];
76 int currentEnd = intervals.get(j)[1];
77
78 // If intervals are not adjacent or overlapping
79 if (end + 1 < currentStart) {
80 mergedIntervals.add(new int[] {start, end});
81 start = currentStart;
82 end = currentEnd;
83 } else {
84 // Merge intervals by extending the end
85 end = Math.max(end, currentEnd);
86 }
87 }
88 // Add the last interval
89 mergedIntervals.add(new int[] {start, end});
90
91 // Build result string with bold tags
92 int currentPos = 0;
93 int intervalIndex = 0;
94 StringBuilder result = new StringBuilder();
95
96 while (currentPos < n) {
97 // All intervals processed, append remaining string
98 if (intervalIndex == mergedIntervals.size()) {
99 result.append(s.substring(currentPos));
100 break;
101 }
102
103 int intervalStart = mergedIntervals.get(intervalIndex)[0];
104 int intervalEnd = mergedIntervals.get(intervalIndex)[1];
105
106 // Append characters before the bold section
107 if (currentPos < intervalStart) {
108 result.append(s.substring(currentPos, intervalStart));
109 }
110
111 // Append bold section with tags
112 intervalIndex++;
113 result.append("<b>");
114 result.append(s.substring(intervalStart, intervalEnd + 1));
115 result.append("</b>");
116
117 // Update position to after the bold section
118 currentPos = intervalEnd + 1;
119 }
120
121 return result.toString();
122 }
123}
124
1class Trie {
2public:
3 vector<Trie*> children;
4 bool isEnd;
5
6 Trie() {
7 children.resize(128); // ASCII characters support
8 isEnd = false;
9 }
10
11 // Insert a word into the trie
12 void insert(string word) {
13 Trie* node = this;
14 for (char c : word) {
15 if (!node->children[c]) {
16 node->children[c] = new Trie();
17 }
18 node = node->children[c];
19 }
20 node->isEnd = true;
21 }
22};
23
24class Solution {
25public:
26 string addBoldTag(string s, vector<string>& words) {
27 // Build trie from dictionary words
28 Trie* trie = new Trie();
29 for (const string& word : words) {
30 trie->insert(word);
31 }
32
33 int n = s.size();
34 vector<pair<int, int>> intervals;
35
36 // Find all matching intervals using trie
37 for (int i = 0; i < n; ++i) {
38 Trie* node = trie;
39 for (int j = i; j < n; ++j) {
40 int charIndex = s[j];
41 if (!node->children[charIndex]) {
42 break; // No matching prefix, stop searching
43 }
44 node = node->children[charIndex];
45 if (node->isEnd) {
46 intervals.push_back({i, j}); // Found a word match [i, j]
47 }
48 }
49 }
50
51 // No words found in string
52 if (intervals.empty()) {
53 return s;
54 }
55
56 // Merge overlapping intervals
57 vector<pair<int, int>> mergedIntervals;
58 int start = intervals[0].first;
59 int end = intervals[0].second;
60
61 for (int i = 1; i < intervals.size(); ++i) {
62 int currentStart = intervals[i].first;
63 int currentEnd = intervals[i].second;
64
65 if (end + 1 < currentStart) {
66 // No overlap, save previous interval
67 mergedIntervals.push_back({start, end});
68 start = currentStart;
69 end = currentEnd;
70 } else {
71 // Overlap or adjacent, extend the interval
72 end = max(end, currentEnd);
73 }
74 }
75 mergedIntervals.push_back({start, end});
76
77 // Build result string with bold tags
78 string result = "";
79 int stringIndex = 0;
80 int intervalIndex = 0;
81
82 while (stringIndex < n) {
83 if (intervalIndex == mergedIntervals.size()) {
84 // No more intervals, append remaining string
85 result += s.substr(stringIndex);
86 break;
87 }
88
89 int intervalStart = mergedIntervals[intervalIndex].first;
90 int intervalEnd = mergedIntervals[intervalIndex].second;
91
92 // Append non-bold part before current interval
93 if (stringIndex < intervalStart) {
94 result += s.substr(stringIndex, intervalStart - stringIndex);
95 }
96
97 // Append bold part
98 result += "<b>";
99 result += s.substr(intervalStart, intervalEnd - intervalStart + 1);
100 result += "</b>";
101
102 stringIndex = intervalEnd + 1;
103 ++intervalIndex;
104 }
105
106 return result;
107 }
108};
109
1// Trie node structure for efficient string matching
2class TrieNode {
3 children: (TrieNode | null)[];
4 isEnd: boolean;
5
6 constructor() {
7 // Support ASCII characters (128 total)
8 this.children = new Array(128).fill(null);
9 this.isEnd = false;
10 }
11}
12
13// Build trie from dictionary words
14function buildTrie(words: string[]): TrieNode {
15 const root = new TrieNode();
16
17 // Insert each word into the trie
18 for (const word of words) {
19 let node = root;
20 for (const char of word) {
21 const charCode = char.charCodeAt(0);
22 if (!node.children[charCode]) {
23 node.children[charCode] = new TrieNode();
24 }
25 node = node.children[charCode]!;
26 }
27 node.isEnd = true;
28 }
29
30 return root;
31}
32
33// Find all matching word intervals in the string using trie
34function findMatchingIntervals(s: string, trie: TrieNode): [number, number][] {
35 const intervals: [number, number][] = [];
36 const n = s.length;
37
38 // Check each position as a potential start of a word
39 for (let i = 0; i < n; i++) {
40 let node: TrieNode | null = trie;
41
42 // Try to match the longest possible word starting at position i
43 for (let j = i; j < n; j++) {
44 const charCode = s.charCodeAt(j);
45 node = node.children[charCode];
46
47 if (!node) {
48 break; // No matching prefix, stop searching
49 }
50
51 if (node.isEnd) {
52 // Found a complete word match from index i to j (inclusive)
53 intervals.push([i, j]);
54 }
55 }
56 }
57
58 return intervals;
59}
60
61// Merge overlapping or adjacent intervals
62function mergeIntervals(intervals: [number, number][]): [number, number][] {
63 if (intervals.length === 0) {
64 return [];
65 }
66
67 const merged: [number, number][] = [];
68 let [start, end] = intervals[0];
69
70 for (let i = 1; i < intervals.length; i++) {
71 const [currentStart, currentEnd] = intervals[i];
72
73 // Check if intervals overlap or are adjacent
74 if (end + 1 < currentStart) {
75 // No overlap, save the previous interval
76 merged.push([start, end]);
77 start = currentStart;
78 end = currentEnd;
79 } else {
80 // Overlap or adjacent, extend the current interval
81 end = Math.max(end, currentEnd);
82 }
83 }
84
85 // Add the last interval
86 merged.push([start, end]);
87
88 return merged;
89}
90
91// Main function to add bold tags around matching words
92function addBoldTag(s: string, words: string[]): string {
93 // Build trie from dictionary words
94 const trie = buildTrie(words);
95
96 // Find all matching intervals using trie
97 const intervals = findMatchingIntervals(s, trie);
98
99 // Return original string if no matches found
100 if (intervals.length === 0) {
101 return s;
102 }
103
104 // Merge overlapping intervals
105 const mergedIntervals = mergeIntervals(intervals);
106
107 // Build result string with bold tags
108 let result = "";
109 let stringIndex = 0;
110 let intervalIndex = 0;
111
112 while (stringIndex < s.length) {
113 // Check if all intervals have been processed
114 if (intervalIndex === mergedIntervals.length) {
115 // No more intervals, append remaining string
116 result += s.substring(stringIndex);
117 break;
118 }
119
120 const [intervalStart, intervalEnd] = mergedIntervals[intervalIndex];
121
122 // Append non-bold part before current interval
123 if (stringIndex < intervalStart) {
124 result += s.substring(stringIndex, intervalStart);
125 }
126
127 // Append bold part with tags
128 result += "<b>";
129 result += s.substring(intervalStart, intervalEnd + 1);
130 result += "</b>";
131
132 // Move to the position after current interval
133 stringIndex = intervalEnd + 1;
134 intervalIndex++;
135 }
136
137 return result;
138}
139
Time and Space Complexity
Time Complexity: O(nΒ² * m + n * k)
- Building the Trie:
O(m * L)
wherem
is the number of words andL
is the average length of words - Finding all matching intervals:
O(nΒ²)
wheren
is the length of strings
- For each starting position
i
(n positions), we potentially check up ton - i
characters - In worst case, this gives us
O(nΒ²)
operations
- For each starting position
- Merging intervals:
O(k log k)
wherek
is the number of intervals found (implicit sorting when processing pairs)- Although not explicitly sorted in this code, the pairs are processed in order due to how they're generated
- Merging itself takes
O(k)
time
- Building the final string:
O(n)
to construct the result
Overall: O(m * L + nΒ² + k log k + n)
which simplifies to O(nΒ² + m * L)
as nΒ²
typically dominates
Space Complexity: O(m * L * 128 + k + n)
- Trie storage:
O(m * L * 128)
where each Trie node has an array of 128 children- In worst case, we store
m * L
nodes, each with 128 pointers
- In worst case, we store
- Intervals storage:
O(k)
wherek
is the number of matching intervals found - Result string construction:
O(n)
for the answer list before joining - Call stack for Trie operations:
O(L)
maximum depth
Overall: O(m * L * 128 + k + n)
which is dominated by the Trie structure's space requirement of O(m * L * 128)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Interval Merging Logic
The Pitfall: One of the most common mistakes is incorrectly determining when intervals should be merged. Developers often forget to handle adjacent intervals (where one interval ends exactly before another begins) or use the wrong comparison operator.
Incorrect Implementation:
# Wrong: Only merges overlapping intervals, misses adjacent ones
if end < curr_start: # Should be end + 1 < curr_start
merged_intervals.append([start, end])
start, end = curr_start, curr_end
else:
end = max(end, curr_end)
Why It's Wrong: Using end < curr_start
only merges intervals that overlap. It misses the case where intervals are adjacent (touching but not overlapping). For example, if we have intervals [0, 2]
and [3, 5]
representing bold regions for indices 0-2 and 3-5, they should be merged into [0, 5]
to create one continuous bold tag <b>...</b>
instead of two separate ones <b>...</b><b>...</b>
.
Correct Implementation:
# Correct: Merges both overlapping AND adjacent intervals
if end + 1 < curr_start: # Gap exists between intervals
merged_intervals.append([start, end])
start, end = curr_start, curr_end
else:
end = max(end, curr_end)
2. Not Sorting Intervals Before Merging
The Pitfall: The code assumes intervals are already sorted by start position, but depending on how intervals are collected, they might not be in order.
Problem Scenario:
# If intervals are collected in this order (not sorted): intervals = [[3, 5], [0, 2], [4, 6]] # Merging without sorting will produce incorrect results
Solution: Always sort intervals before merging:
# Sort intervals by start position, then by end position intervals.sort() # Python's sort handles tuples/lists correctly
3. Off-by-One Errors in String Slicing
The Pitfall: Confusion between inclusive and exclusive indices when building the result string.
Incorrect:
# Wrong: Forgets that interval_end is inclusive result.append(s[interval_start:interval_end]) # Missing the last character
Correct:
# Correct: Include the character at interval_end result.append(s[interval_start:interval_end + 1])
4. Handling Empty Input Cases
The Pitfall: Not properly handling edge cases like empty strings or empty word lists.
Solution: Add validation at the beginning:
if not s or not words: return s
5. Trie Size Assumption
The Pitfall: Using a fixed-size array of 128 for ASCII characters might fail for Unicode strings or special characters beyond standard ASCII range.
Better Approach for Unicode:
class Trie:
def __init__(self):
# Use dictionary instead of fixed array for Unicode support
self.children = {}
self.is_end = False
def insert(self, word):
node = self
for char in word:
if char not in node.children:
node.children[char] = Trie()
node = node.children[char]
node.is_end = True
Complete Corrected Solution with Safeguards:
class Solution:
def addBoldTag(self, s: str, words: List[str]) -> str:
# Handle edge cases
if not s or not words:
return s
# Build trie and find intervals (same as before)
trie = Trie()
for word in words:
trie.insert(word)
n = len(s)
intervals = []
for i in range(n):
node = trie
for j in range(i, n):
index = ord(s[j])
if node.children[index] is None:
break
node = node.children[index]
if node.is_end:
intervals.append([i, j])
if not intervals:
return s
# IMPORTANT: Sort intervals before merging
intervals.sort()
# Merge with correct adjacency check
merged = []
start, end = intervals[0]
for curr_start, curr_end in intervals[1:]:
if end + 1 < curr_start: # Correct adjacency check
merged.append([start, end])
start, end = curr_start, curr_end
else:
end = max(end, curr_end)
merged.append([start, end])
# Build result with correct string slicing
result = []
i = 0
for start, end in merged:
if i < start:
result.append(s[i:start])
result.append('<b>')
result.append(s[start:end + 1]) # +1 for inclusive end
result.append('</b>')
i = end + 1
if i < n:
result.append(s[i:])
return ''.join(result)
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
Trie Introduction Imagine you have a magic dictionary where as soon as you start spelling a word it immediately tells you how many words that start with those letters How cool would that be This is exactly the kind of magic a data structure called trie performs What's a Trie
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!