Problem#
We are given a rooted binary tree whose nodes store single characters. Every root → leaf path spells a complete word (each leaf terminates exactly one word). Given a target word W
, determine whether the tree contains W
as one of those root-to-leaf paths.
We contrast:
A brute-force DFS that enumerates all root→leaf words and compares each to W
.
A pruned backtracking DFS that aborts a branch immediately upon character mismatch at some depth.
Unlike a trie (multi-way branching keyed by character), here each node has at most two children (left
, right
), so pruning cannot pick a unique next child by character; it only stops early when mismatch occurs.
Examples#
Example 1#
graph TD;
A(A):::green --- N(N) & I(I):::green
N --- T(T) & D(D)
I --- M(M):::green & R(R)
T ~~~ W1["ANT"]
D ~~~ W2["AND"]
M ~~~ W3["AIM"]:::green
R ~~~ W4["AIR"]
classDef green fill:#3CB371,stroke:#000,stroke-width:1px,color:#fff;
1
2
3
Input: root = [ A, N, I, T, D, M, R], word = AIM
Output: true
Explanation: Root→ A→ I→ M path exists and ends at a leaf.
Example 2#
1
2
3
Input: root = [ A, N, I, T, D, M, R], word = ARC
Output: false
Explanation: After A→ R node, subtree does not contain a C completing a leaf word ARC.
Similar Problems
Solution#
Method 1 - Brute Force Enumeration#
Intuition#
Traverse every root→leaf path, build the word string, and compare with the target. Return true if any matches; otherwise false after full traversal.
Approach#
Perform DFS from root; maintain path characters in a buffer.
When a node is a leaf (both children null), the buffer forms a full word.
Compare with target; if equal → return true / set flag.
Otherwise explore left then right.
After recursion, pop the character (backtracking) and continue; return false if no leaf word matches.
Visualization#
graph LR;
subgraph S1[" "]
A(A):::orange--- N(N):::orange & I(I)
N --- T(T):::orange & D(D)
I --- M(M) & R(R)
T ~~~ W1["ANT != AIM"]:::orange
D ~~~ W2["AND"]
M ~~~ W3["AIM"]
R ~~~ W4["AIR"]
end
subgraph S2[" "]
A2(A):::orange--- N2(N):::orange & I2(I)
N2 --- T2(T) & D2(D):::orange
I2 --- M2(M) & R2(R)
T2 ~~~ W12["ANT"]
D2 ~~~ W22["AND != AIM"]:::orange
M2 ~~~ W32["AIM"]
R2 ~~~ W42["AIR"]
end
subgraph S3[" "]
A3(A):::green--- N3(N) & I3(I):::green
N3 --- T3(T) & D3(D)
I3 --- M3(M):::green & R3(R)
T3 ~~~ W13["ANT"]
D3 ~~~ W23["AND"]
M3 ~~~ W33["AIM == AIM"]:::green
R3 ~~~ W43["AIR"]
end
S1 ~~~ S2 ~~~ S3
classDef green fill:#3CB371,stroke:#000,stroke-width:1px,color:#fff;
classDef orange fill:#FFA07A,stroke:#333,stroke-width:2px;
Pseudocode#
1
2
3
4
5
6
7
8
9
10
11
12
13
function brute(root, target):
found = false
path = []
def dfs (node):
if node is null or found: return
path. append(node. char)
if node. left is null and node. right is null: # leaf
if join(path) == target: found = true
dfs(node. left)
dfs(node. right)
path. pop()
dfs(root)
return found
Code#
Cpp
Java
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <string>
using namespace std;
struct Node {
char ch;
Node* left;
Node* right;
Node(char c) : ch(c), left(nullptr ), right(nullptr ) {}
};
class Solution {
public :
bool searchWordBrute(Node* root, const string& target) {
if (! root) return false;
bool found = false;
string path;
dfs(root, target, path, found);
return found;
}
private :
void dfs(Node* node, const string& target, string& path, bool & found) {
if (! node || found) return ;
path.push_back(node-> ch);
if (! node-> left && ! node-> right) { // leaf
if (path == target) found = true;
}
dfs(node-> left, target, path, found);
dfs(node-> right, target, path, found);
path.pop_back();
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Node {
char ch;
Node left, right;
Node(char c) { this .ch = c; }
}
class Solution {
public boolean searchWordBrute (Node root, String target) {
if (root == null ) return false ;
StringBuilder path = new StringBuilder();
boolean [] found = new boolean [ 1] ;
dfs(root, target, path, found);
return found[ 0] ;
}
private void dfs (Node node, String target, StringBuilder path, boolean [] found) {
if (node == null || found[ 0] ) return ;
path.append (node.ch );
if (node.left == null && node.right == null ) {
if (path.toString ().equals (target)) found[ 0] = true ;
}
dfs(node.left , target, path, found);
dfs(node.right , target, path, found);
path.deleteCharAt (path.length () - 1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from typing import Optional
class Node :
def __init__ (self, ch: str):
self. ch = ch
self. left: Optional['Node' ] = None
self. right: Optional['Node' ] = None
class Solution :
def searchWordBrute (self, root: Optional[Node], target: str) -> bool:
if root is None :
return False
found = False
path: list[str] = []
def dfs (node: Optional[Node]):
nonlocal found
if node is None or found:
return
path. append(node. ch)
if node. left is None and node. right is None :
if '' . join(path) == target:
found = True
dfs(node. left)
dfs(node. right)
path. pop()
dfs(root)
return found
Complexity#
⏰ Time complexity: O(T)
– visits every node; T
= total nodes (sum of all word lengths) in worst case.
🧺 Space complexity: O(L)
– recursion/path stack length = height L
(max word length).
Trade-offs#
Simple but wasteful: constructs and compares every word regardless of early mismatches.
Cannot stop early on mismatching prefixes (other than after full leaf words).
Method 2 - Backtracking with Early Pruning#
Intuition#
Descend only as long as the current depth character matches the target at that position. If mismatch occurs, immediately backtrack—eliminating entire subtrees that cannot possibly yield the target word (pruning).
Approach#
Maintain index i
in target
during DFS.
If node is null or node.ch != target[i]
→ prune (return False).
If i == len(target)-1
, return True only if node is a leaf (no children).
Otherwise explore left and right children; return True if either succeeds.
Pseudocode#
1
2
3
4
5
6
7
8
9
function pruned(root, target):
if root is null or target == "" : return false
def dfs (node, i):
if node is null: return false
if node. char != target[i]: return false
if i == len(target)- 1 :
return node. left is null and node. right is null
return dfs(node. left, i+ 1 ) or dfs(node. right, i+ 1 )
return dfs(root, 0 )
Visualization#
Pruned search halts at the node where the next letter would be N
(not I
), immediately backtracking and skipping the entire N
subtree.
graph LR;
subgraph S1[" "]
A(A):::green--- N(N):::orange & I(I)
N --- T(T) & D(D)
I --- M(M) & R(R)
N -.->|"backtrack as AN != AI"| A
T ~~~ W1["ANT"]
D ~~~ W2["AND"]
M ~~~ W3["AIM"]
R ~~~ W4["AIR"]
end
subgraph S3[" "]
A3(A):::green--- N3(N) & I3(I):::green
N3 --- T3(T) & D3(D)
I3 --- M3(M):::green & R3(R)
T3 ~~~ W13["ANT"]
D3 ~~~ W23["AND"]
M3 ~~~ W33["AIM"]:::green
R3 ~~~ W43["AIR"]
end
S1 ~~~ S2 ~~~ S3
classDef green fill:#3CB371,stroke:#000,stroke-width:1px,color:#fff;
classDef orange fill:#FFA07A,stroke:#333,stroke-width:2px;
Code#
Cpp
Java
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SolutionPruned {
public :
bool searchWordPruned(Node* root, const string& target) {
if (! root || target.empty()) return false;
return dfs (root, target, 0 );
}
private :
bool dfs(Node* node, const string& target, int i) {
if (! node) return false;
if (node-> ch != target[i]) return false;
if (i == (int )target.size() - 1 )
return ! node-> left && ! node-> right; // leaf check
return dfs (node-> left, target, i + 1 ) || dfs(node-> right, target, i + 1 );
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
class SolutionPruned {
public boolean searchWordPruned (Node root, String target) {
if (root == null || target.isEmpty ()) return false ;
return dfs(root, target, 0);
}
private boolean dfs (Node node, String target, int i) {
if (node == null ) return false ;
if (node.ch != target.charAt (i)) return false ;
if (i == target.length () - 1)
return node.left == null && node.right == null ;
return dfs(node.left , target, i + 1) || dfs(node.right , target, i + 1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SolutionPruned :
def searchWordPruned (self, root: Optional[Node], target: str) -> bool:
if root is None or not target:
return False
def dfs (node: Optional[Node], i: int) -> bool:
if node is None :
return False
if node. ch != target[i]:
return False
if i == len(target) - 1 :
return node. left is None and node. right is None
return dfs(node. left, i + 1 ) or dfs(node. right, i + 1 )
return dfs(root, 0 )
Complexity#
⏰ Time complexity: O(L)
along successful path or early mismatch; worst-case O(T)
if many nodes share the target prefix characters (must explore all).
🧺 Space complexity: O(L)
– recursion depth equals path length.
Trade-offs#
Much faster in practice when branches diverge early.
Slightly more logic; assumes children accessible by character (hash map) for O(1)
next-step selection.
Summary#
Backtracking here prunes binary tree branches immediately on character mismatch, illustrating the pruning principle: discard any subtree whose partial path can no longer match the target.