🧠
Trie + DFS with Early Pruning and Result Collection
💡 This approach refines the previous by explicitly using DFS/backtracking on the trie to find all matches starting at each index, emphasizing backtracking skills.
Intuition
Use DFS from each text index to traverse trie nodes, collecting all word ends found along the path.
Algorithm
- Build a trie from words
- For each index i in text, call DFS starting at root and index i
- In DFS, if current node is word end, record [start, current_index]
- Recurse to child nodes matching next character in text until no match
💡 DFS allows clean recursive exploration of all possible matches starting at each index, with pruning when no child matches.
class TrieNode:
def __init__(self):
self.children = {}
self.word_end = False
class Solution:
def indexPairs(self, text, words):
root = TrieNode()
for w in words:
node = root
for c in w:
if c not in node.children:
node.children[c] = TrieNode()
node = node.children[c]
node.word_end = True
res = []
n = len(text)
def dfs(node, i, start):
if node.word_end:
res.append([start, i-1])
if i == n:
return
c = text[i]
if c in node.children:
dfs(node.children[c], i+1, start)
for i in range(n):
dfs(root, i, i)
res.sort()
return res
# Example usage
if __name__ == '__main__':
sol = Solution()
text = "thestoryofleetcodeandme"
words = ["story","fleet","leetcode"]
print(sol.indexPairs(text, words))
Line Notes
def dfs(node, i, start)Recursive DFS to explore trie from index i
if node.word_endRecord pair when word ends at current node
if i == n: returnStop recursion if end of text reached
if c in node.childrenRecurse only if next character matches a child
for i in range(n)Start DFS from each index in text
import java.util.*;
class TrieNode {
Map<Character, TrieNode> children = new HashMap<>();
boolean wordEnd = false;
}
public class Solution {
TrieNode root = new TrieNode();
public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
node.children.putIfAbsent(c, new TrieNode());
node = node.children.get(c);
}
node.wordEnd = true;
}
public void dfs(String text, int i, int start, TrieNode node, List<List<Integer>> res) {
if (node.wordEnd) res.add(Arrays.asList(start, i - 1));
if (i == text.length()) return;
char c = text.charAt(i);
if (node.children.containsKey(c)) {
dfs(text, i + 1, start, node.children.get(c), res);
}
}
public List<List<Integer>> indexPairs(String text, String[] words) {
for (String w : words) insert(w);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < text.length(); i++) {
dfs(text, i, i, root, res);
}
res.sort((a,b) -> a.get(0) != b.get(0) ? a.get(0)-b.get(0) : a.get(1)-b.get(1));
return res;
}
public static void main(String[] args) {
Solution sol = new Solution();
String text = "thestoryofleetcodeandme";
String[] words = {"story","fleet","leetcode"};
System.out.println(sol.indexPairs(text, words));
}
}
Line Notes
public void dfs(...)Recursive DFS method to explore trie matches
if (node.wordEnd)Add pair when word ends at current node
if (i == text.length()) returnStop recursion at end of text
if (node.children.containsKey(c))Recurse only if next char matches child
for (int i = 0; i < text.length(); i++)Start DFS from each index
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;
struct TrieNode {
unordered_map<char, TrieNode*> children;
bool wordEnd = false;
};
class Solution {
TrieNode* root = new TrieNode();
public:
void insert(const string& word) {
TrieNode* node = root;
for (char c : word) {
if (!node->children.count(c))
node->children[c] = new TrieNode();
node = node->children[c];
}
node->wordEnd = true;
}
void dfs(const string& text, int i, int start, TrieNode* node, vector<vector<int>>& res) {
if (node->wordEnd) res.push_back({start, i - 1});
if (i == (int)text.size()) return;
char c = text[i];
if (node->children.count(c)) {
dfs(text, i + 1, start, node->children[c], res);
}
}
vector<vector<int>> indexPairs(string text, vector<string>& words) {
for (auto& w : words) insert(w);
vector<vector<int>> res;
for (int i = 0; i < (int)text.size(); i++) {
dfs(text, i, i, root, res);
}
sort(res.begin(), res.end());
return res;
}
};
int main() {
Solution sol;
string text = "thestoryofleetcodeandme";
vector<string> words = {"story","fleet","leetcode"};
vector<vector<int>> res = sol.indexPairs(text, words);
for (auto &p : res) {
cout << "[" << p[0] << "," << p[1] << "] ";
}
cout << endl;
return 0;
}
Line Notes
void dfs(...)Recursive DFS to find all matches starting at index
if (node->wordEnd)Record pair when word ends here
if (i == (int)text.size()) returnStop recursion at text end
if (node->children.count(c))Recurse only if next char matches child
for (int i = 0; i < (int)text.size(); i++)Start DFS from each index
class TrieNode {
constructor() {
this.children = new Map();
this.wordEnd = false;
}
}
var indexPairs = function(text, words) {
const root = new TrieNode();
for (const w of words) {
let node = root;
for (const c of w) {
if (!node.children.has(c)) node.children.set(c, new TrieNode());
node = node.children.get(c);
}
node.wordEnd = true;
}
const res = [];
const n = text.length;
function dfs(node, i, start) {
if (node.wordEnd) res.push([start, i - 1]);
if (i === n) return;
const c = text[i];
if (node.children.has(c)) {
dfs(node.children.get(c), i + 1, start);
}
}
for (let i = 0; i < n; i++) {
dfs(root, i, i);
}
res.sort((a,b) => a[0] - b[0] || a[1] - b[1]);
return res;
};
// Example usage
console.log(indexPairs("thestoryofleetcodeandme", ["story","fleet","leetcode"]));
Line Notes
function dfs(node, i, start)Recursive DFS to explore trie matches
if (node.wordEnd)Add pair when word ends at current node
if (i === n) returnStop recursion at end of text
if (node.children.has(c))Recurse only if next char matches child
for (let i = 0; i < n; i++)Start DFS from each index
TimeO(n * m) where n = length of text, m = max length of words
SpaceO(w * m) for trie storage, w = number of words
DFS explores trie paths from each index, pruning early when no match, so worst case similar to approach 2.
💡 This approach is a clean recursive way to find matches, useful to demonstrate backtracking skills.
Interview Verdict: Accepted / Clean recursive solution
This approach is elegant and shows mastery of trie traversal and recursion, good for interviews.