Before attempting this problem, you should be comfortable with:
Binary Tree Traversal - Understanding root-to-leaf paths and tree structure
Depth First Search (DFS) - Recursive and iterative tree traversal techniques
Palindrome Properties - A sequence can form a palindrome if at most one character has an odd count
Bit Manipulation - Using XOR to toggle bits and checking if at most one bit is set
Backtracking - Undoing state changes when returning from recursive calls
1. Depth First Search
Intuition
A path can form a palindrome if at most one digit has an odd count (that digit would go in the middle). As we traverse from root to leaf, we track the count of each digit and maintain a running count of how many digits have odd frequency. At each leaf, we check if the odd count is at most 1.
Algorithm
Initialize a frequency map for digits (1-9) and an odd counter.
Define dfs(node):
If null, return 0.
Increment the count for node.val. Update odd accordingly (increment if count became odd, decrement if it became even).
If it is a leaf node, return 1 if odd <= 1, else 0.
Otherwise, recurse on both children and sum results.
Before returning, undo the changes (decrement count, restore odd).
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @return {number}
*/pseudoPalindromicPaths(root){const count =newMap();let odd =0;constdfs=(cur)=>{if(!cur)return0;
count.set(cur.val,(count.get(cur.val)||0)+1);let odd_change = count.get(cur.val)%2===1?1:-1;
odd += odd_change;let res;if(!cur.left &&!cur.right){
res = odd <=1?1:0;}else{
res =dfs(cur.left)+dfs(cur.right);}
odd -= odd_change;
count.set(cur.val, count.get(cur.val)-1);return res;};returndfs(root);}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintPseudoPalindromicPaths(TreeNode root){Dictionary<int,int> count =newDictionary<int,int>();int odd =0;returnDfs(root, count,ref odd);}privateintDfs(TreeNode cur,Dictionary<int,int> count,refint odd){if(cur ==null)return0;if(!count.ContainsKey(cur.val)) count[cur.val]=0;
count[cur.val]++;int oddChange =(count[cur.val]%2==1)?1:-1;
odd += oddChange;int res;if(cur.left ==null&& cur.right ==null){
res =(odd <=1)?1:0;}else{
res =Dfs(cur.left, count,ref odd)+Dfs(cur.right, count,ref odd);}
odd -= oddChange;
count[cur.val]--;return res;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcpseudoPalindromicPaths(root *TreeNode)int{
count :=make(map[int]int)
odd :=0var dfs func(cur *TreeNode)int
dfs =func(cur *TreeNode)int{if cur ==nil{return0}
count[cur.Val]++
oddChange :=1if count[cur.Val]%2==0{
oddChange =-1}
odd += oddChange
var res intif cur.Left ==nil&& cur.Right ==nil{if odd <=1{
res =1}else{
res =0}}else{
res =dfs(cur.Left)+dfs(cur.Right)}
odd -= oddChange
count[cur.Val]--return res
}returndfs(root)}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funpseudoPalindromicPaths(root: TreeNode?): Int {val count = mutableMapOf<Int, Int>()var odd =0fundfs(cur: TreeNode?): Int {if(cur ==null)return0
count[cur.`val`]=(count[cur.`val`]?:0)+1val oddChange =if(count[cur.`val`]!!%2==1)1else-1
odd += oddChange
val res =if(cur.left ==null&& cur.right ==null){if(odd <=1)1else0}else{dfs(cur.left)+dfs(cur.right)}
odd -= oddChange
count[cur.`val`]= count[cur.`val`]!!-1return res
}returndfs(root)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcpseudoPalindromicPaths(_ root:TreeNode?)->Int{var count =[Int:Int]()var odd =0funcdfs(_ cur:TreeNode?)->Int{guardlet cur = cur else{return0}
count[cur.val,default:0]+=1let oddChange =(count[cur.val]!%2==1)?1:-1
odd += oddChange
let res:Intif cur.left==nil&& cur.right==nil{
res = odd <=1?1:0}else{
res =dfs(cur.left)+dfs(cur.right)}
odd -= oddChange
count[cur.val]!-=1return res
}returndfs(root)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(h) for recursion stack.
Where n is the number of nodes and h is the height of the given tree.
2. Depth First Search (Using Array)
Intuition
Instead of a hash map, we use a fixed-size array since node values are limited to 1-9. We track odd/even counts using XOR: toggling a bit each time a digit appears. This gives a cleaner way to track parity while maintaining the same core logic of counting odd-frequency digits.
Algorithm
Create an array count[10] initialized to 0, and an odd counter.
Define dfs(node, odd):
If null, return 0.
Toggle count[node.val] using XOR. Update odd based on whether the count is now odd or even.
If it is a leaf and odd <= 1, return 1. Otherwise recurse on children.
Restore odd and toggle count[node.val] back before returning.
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @return {number}
*/pseudoPalindromicPaths(root){const count =newArray(10).fill(0);constdfs=(cur, odd)=>{if(!cur)return0;
count[cur.val]^=1;
odd += count[cur.val]===1?1:-1;let res =!cur.left &&!cur.right && odd <=1?1:dfs(cur.left, odd)+dfs(cur.right, odd);
odd += count[cur.val]===1?1:-1;
count[cur.val]^=1;return res;};returndfs(root,0);}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintPseudoPalindromicPaths(TreeNode root){int[] count =newint[10];returnDfs(root, count,0);}privateintDfs(TreeNode cur,int[] count,int odd){if(cur ==null)return0;
count[cur.val]^=1;
odd += count[cur.val]==1?1:-1;int res =(cur.left ==null&& cur.right ==null&& odd <=1)?1:Dfs(cur.left, count, odd)+Dfs(cur.right, count, odd);
odd += count[cur.val]==1?1:-1;
count[cur.val]^=1;return res;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcpseudoPalindromicPaths(root *TreeNode)int{
count :=make([]int,10)var dfs func(cur *TreeNode, odd int)int
dfs =func(cur *TreeNode, odd int)int{if cur ==nil{return0}
count[cur.Val]^=1if count[cur.Val]==1{
odd++}else{
odd--}var res intif cur.Left ==nil&& cur.Right ==nil&& odd <=1{
res =1}else{
res =dfs(cur.Left, odd)+dfs(cur.Right, odd)}if count[cur.Val]==1{
odd++}else{
odd--}
count[cur.Val]^=1return res
}returndfs(root,0)}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funpseudoPalindromicPaths(root: TreeNode?): Int {val count =IntArray(10)fundfs(cur: TreeNode?, odd: Int): Int {if(cur ==null)return0
count[cur.`val`]= count[cur.`val`]xor1var newOdd = odd +if(count[cur.`val`]==1)1else-1val res =if(cur.left ==null&& cur.right ==null&& newOdd <=1){1}else{dfs(cur.left, newOdd)+dfs(cur.right, newOdd)}
newOdd +=if(count[cur.`val`]==1)1else-1
count[cur.`val`]= count[cur.`val`]xor1return res
}returndfs(root,0)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcpseudoPalindromicPaths(_ root:TreeNode?)->Int{var count =[Int](repeating:0, count:10)funcdfs(_ cur:TreeNode?,_ odd:Int)->Int{guardlet cur = cur else{return0}
count[cur.val]^=1var newOdd = odd +(count[cur.val]==1?1:-1)let res:Intif cur.left==nil&& cur.right==nil&& newOdd <=1{
res =1}else{
res =dfs(cur.left, newOdd)+dfs(cur.right, newOdd)}
newOdd +=(count[cur.val]==1?1:-1)
count[cur.val]^=1return res
}returndfs(root,0)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(h) for recursion stack.
Where n is the number of nodes and h is the height of the given tree.
3. Depth First Search (Bit Mask)
Intuition
We can encode all parity information in a single integer using bits. Each bit position represents a digit, and the bit is 1 if that digit has appeared an odd number of times. XOR toggles the bit on each occurrence. At a leaf, if the path is pseudo-palindromic, at most one bit should be set. We check this with the trick: path & (path - 1) == 0 (true for 0 or exactly one bit set).
Algorithm
Define dfs(node, path):
If null, return 0.
Toggle the bit for node.val: path ^= (1 << node.val).
If it is a leaf, return 1 if (path & (path - 1)) == 0, else 0.
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @return {number}
*/pseudoPalindromicPaths(root){constdfs=(node, path)=>{if(!node)return0;
path ^=1<< node.val;if(!node.left &&!node.right){return(path &(path -1))===0?1:0;}returndfs(node.left, path)+dfs(node.right, path);};returndfs(root,0);}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintPseudoPalindromicPaths(TreeNode root){returnDfs(root,0);}privateintDfs(TreeNode node,int path){if(node ==null)return0;
path ^=(1<< node.val);if(node.left ==null&& node.right ==null){return(path &(path -1))==0?1:0;}returnDfs(node.left, path)+Dfs(node.right, path);}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcpseudoPalindromicPaths(root *TreeNode)int{var dfs func(node *TreeNode, path int)int
dfs =func(node *TreeNode, path int)int{if node ==nil{return0}
path ^=(1<< node.Val)if node.Left ==nil&& node.Right ==nil{if path&(path-1)==0{return1}return0}returndfs(node.Left, path)+dfs(node.Right, path)}returndfs(root,0)}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funpseudoPalindromicPaths(root: TreeNode?): Int {fundfs(node: TreeNode?, path: Int): Int {if(node ==null)return0val newPath = path xor(1shl node.`val`)if(node.left ==null&& node.right ==null){returnif(newPath and(newPath -1)==0)1else0}returndfs(node.left, newPath)+dfs(node.right, newPath)}returndfs(root,0)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcpseudoPalindromicPaths(_ root:TreeNode?)->Int{funcdfs(_ node:TreeNode?,_ path:Int)->Int{guardlet node = node else{return0}let newPath = path ^(1<< node.val)if node.left==nil&& node.right==nil{return(newPath &(newPath -1))==0?1:0}returndfs(node.left, newPath)+dfs(node.right, newPath)}returndfs(root,0)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(h) for recursion stack.
Where n is the number of nodes and h is the height of the given tree.
4. Breadth First Search
Intuition
BFS traverses level by level using a queue. We pair each node with its current path bitmask. When we reach a leaf, we check if the path can form a palindrome using the same bit trick. This approach uses more space than DFS but processes nodes in breadth-first order.
Algorithm
Initialize a queue with (root, 0).
While the queue is not empty:
Dequeue (node, path).
Update path ^= (1 << node.val).
If it is a leaf, increment the count if (path & (path - 1)) == 0.
Otherwise, enqueue children with the updated path.
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @return {number}
*/pseudoPalindromicPaths(root){let res =0;const q =newQueue([[root,0]]);while(!q.isEmpty()){const[node, path]= q.pop();const newPath = path ^(1<< node.val);if(!node.left &&!node.right){if((newPath &(newPath -1))===0) res++;continue;}if(node.left) q.push([node.left, newPath]);if(node.right) q.push([node.right, newPath]);}return res;}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintPseudoPalindromicPaths(TreeNode root){int res =0;Queue<(TreeNode node,int path)> q =newQueue<(TreeNode,int)>();
q.Enqueue((root,0));while(q.Count >0){var(node, path)= q.Dequeue();int newPath = path ^(1<< node.val);if(node.left ==null&& node.right ==null){if((newPath &(newPath -1))==0) res++;continue;}if(node.left !=null) q.Enqueue((node.left, newPath));if(node.right !=null) q.Enqueue((node.right, newPath));}return res;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcpseudoPalindromicPaths(root *TreeNode)int{
res :=0type pair struct{
node *TreeNode
path int}
queue :=[]pair{{root,0}}forlen(queue)>0{
p := queue[0]
queue = queue[1:]
node, path := p.node, p.path
newPath := path ^(1<< node.Val)if node.Left ==nil&& node.Right ==nil{if newPath&(newPath-1)==0{
res++}continue}if node.Left !=nil{
queue =append(queue, pair{node.Left, newPath})}if node.Right !=nil{
queue =append(queue, pair{node.Right, newPath})}}return res
}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funpseudoPalindromicPaths(root: TreeNode?): Int {if(root ==null)return0var res =0val queue = ArrayDeque<Pair<TreeNode, Int>>()
queue.add(Pair(root,0))while(queue.isNotEmpty()){val(node, path)= queue.removeFirst()val newPath = path xor(1shl node.`val`)if(node.left ==null&& node.right ==null){if(newPath and(newPath -1)==0) res++continue}
node.left?.let{ queue.add(Pair(it, newPath))}
node.right?.let{ queue.add(Pair(it, newPath))}}return res
}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcpseudoPalindromicPaths(_ root:TreeNode?)->Int{guardlet root = root else{return0}var res =0var queue:[(TreeNode,Int)]=[(root,0)]while!queue.isEmpty {let(node, path)= queue.removeFirst()let newPath = path ^(1<< node.val)if node.left==nil&& node.right==nil{if newPath &(newPath -1)==0{
res +=1}continue}ifletleft= node.left{
queue.append((left, newPath))}ifletright= node.right{
queue.append((right, newPath))}}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
5. Iterative DFS
Intuition
We can simulate recursive DFS using an explicit stack. Each stack entry contains a node and the path bitmask accumulated so far. This avoids recursion overhead and potential stack overflow for very deep trees, while maintaining the same DFS traversal order.
Algorithm
Initialize a stack with (root, 0).
While the stack is not empty:
Pop (node, path).
Update path ^= (1 << node.val).
If it is a leaf, increment the count if (path & (path - 1)) == 0.
Otherwise, push children (with updated path) onto the stack.
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintPseudoPalindromicPaths(TreeNode root){int count =0;Stack<(TreeNode node,int path)> stack =newStack<(TreeNode,int)>();
stack.Push((root,0));while(stack.Count >0){var(node, path)= stack.Pop();int newPath = path ^(1<< node.val);if(node.left ==null&& node.right ==null){if((newPath &(newPath -1))==0) count++;}else{if(node.right !=null) stack.Push((node.right, newPath));if(node.left !=null) stack.Push((node.left, newPath));}}return count;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcpseudoPalindromicPaths(root *TreeNode)int{
count :=0type pair struct{
node *TreeNode
path int}
stack :=[]pair{{root,0}}forlen(stack)>0{
p := stack[len(stack)-1]
stack = stack[:len(stack)-1]
node, path := p.node, p.path
newPath := path ^(1<< node.Val)if node.Left ==nil&& node.Right ==nil{if newPath&(newPath-1)==0{
count++}}else{if node.Right !=nil{
stack =append(stack, pair{node.Right, newPath})}if node.Left !=nil{
stack =append(stack, pair{node.Left, newPath})}}}return count
}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funpseudoPalindromicPaths(root: TreeNode?): Int {if(root ==null)return0var count =0val stack = ArrayDeque<Pair<TreeNode, Int>>()
stack.addLast(Pair(root,0))while(stack.isNotEmpty()){val(node, path)= stack.removeLast()val newPath = path xor(1shl node.`val`)if(node.left ==null&& node.right ==null){if(newPath and(newPath -1)==0) count++}else{
node.right?.let{ stack.addLast(Pair(it, newPath))}
node.left?.let{ stack.addLast(Pair(it, newPath))}}}return count
}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcpseudoPalindromicPaths(_ root:TreeNode?)->Int{guardlet root = root else{return0}var count =0var stack:[(TreeNode,Int)]=[(root,0)]while!stack.isEmpty {let(node, path)= stack.removeLast()let newPath = path ^(1<< node.val)if node.left==nil&& node.right==nil{if newPath &(newPath -1)==0{
count +=1}}else{ifletright= node.right{
stack.append((right, newPath))}ifletleft= node.left{
stack.append((left, newPath))}}}return count
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(h)
Where n is the number of nodes and h is the height of the given tree.
Common Pitfalls
Forgetting to Backtrack State
When using DFS with a shared count array or bitmask, failing to restore the state after returning from recursive calls corrupts the path information for sibling subtrees. Always undo the toggle operation (XOR back or decrement count) before returning from the current node.
Checking Palindrome Condition at Non-Leaf Nodes
The pseudo-palindrome check should only occur at leaf nodes (where both left and right children are null). Checking at internal nodes leads to counting incomplete paths and produces incorrect results. Always verify node.left == null && node.right == null before evaluating the palindrome condition.
Misunderstanding the Palindrome Condition
A path can form a palindrome if at most one digit has an odd count (the middle element for odd-length palindromes). Using odd == 0 instead of odd <= 1 misses valid paths with odd length. The bitmask check path & (path - 1) == 0 correctly identifies zero or one bit set.