Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1457. Pseudo-Palindromic Paths in a Binary Tree - Leetcode Solution

Code Implementation

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def pseudoPalindromicPaths (self, root: TreeNode) -> int:
        def dfs(node, path):
            if not node:
                return 0
            path ^= 1 << node.val
            if not node.left and not node.right:
                return int(path & (path - 1) == 0)
            return dfs(node.left, path) + dfs(node.right, path)
        return dfs(root, 0)
      
// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
    int pseudoPalindromicPaths (TreeNode* root) {
        return dfs(root, 0);
    }
    
    int dfs(TreeNode* node, int path) {
        if (!node) return 0;
        path ^= (1 << node->val);
        if (!node->left && !node->right) {
            return (path & (path - 1)) == 0;
        }
        return dfs(node->left, path) + dfs(node->right, path);
    }
};
      
// Definition for a binary tree node.
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

class Solution {
    public int pseudoPalindromicPaths (TreeNode root) {
        return dfs(root, 0);
    }
    
    private int dfs(TreeNode node, int path) {
        if (node == null) return 0;
        path ^= (1 << node.val);
        if (node.left == null && node.right == null) {
            return (path & (path - 1)) == 0 ? 1 : 0;
        }
        return dfs(node.left, path) + dfs(node.right, path);
    }
}
      
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

var pseudoPalindromicPaths  = function(root) {
    function dfs(node, path) {
        if (!node) return 0;
        path ^= (1 << node.val);
        if (!node.left && !node.right) {
            return (path & (path - 1)) === 0 ? 1 : 0;
        }
        return dfs(node.left, path) + dfs(node.right, path);
    }
    return dfs(root, 0);
};
      

Problem Description

Given a binary tree where each node contains a digit from 1 to 9, you are to count the number of root-to-leaf paths where the sequence of node values along the path can be rearranged to form a palindrome. Such a path is called a pseudo-palindromic path.

  • A palindrome is a sequence that reads the same backward as forward. For a sequence to be rearranged into a palindrome, at most one digit can appear an odd number of times; the rest must appear an even number of times.
  • Your task is to return the count of root-to-leaf paths in the tree that are pseudo-palindromic.

Constraints:

  • The binary tree has between 1 and 10^5 nodes.
  • Each node's value is an integer from 1 to 9.

Thought Process

The problem asks us to find paths from the root to every leaf where the node values could be rearranged to form a palindrome. The key insight is that for a sequence to be rearranged as a palindrome, all digits must appear an even number of times, except possibly one digit (which can appear an odd number of times in the center of the palindrome).

A brute-force approach would be to enumerate every root-to-leaf path, count the frequency of each digit along the path, and check if at most one digit has an odd count. However, this approach would be inefficient, especially for large trees.

To optimize, we can use bit manipulation to efficiently keep track of the parity (even or odd) of the counts of digits along the current path. Instead of storing the full count, we just need to know whether the count is odd or even. This can be efficiently represented using a single integer as a bitmask.

Solution Approach

We will use depth-first search (DFS) to traverse the binary tree. At each node, we update a bitmask representing the parity of the count of each digit along the current path. Here's how the solution works step-by-step:

  1. Bitmask Representation: We use an integer path where the i-th bit is set if the digit i has been seen an odd number of times along the current path. Since digits are from 1 to 9, we only need 9 bits.
  2. DFS Traversal: We start DFS from the root. At each node, we toggle the bit corresponding to the node's value in path using XOR.
  3. Leaf Check: When we reach a leaf node, we check if the current path represents a pseudo-palindromic sequence. This is true if at most one bit is set in path (i.e., path & (path - 1) == 0).
  4. Recursive Accumulation: We recursively sum the number of pseudo-palindromic paths from the left and right subtrees.
  5. Return Result: The total count is returned as the final answer.

This approach is efficient because bitwise operations are fast, and we do not need to store the entire path or count arrays for each recursion.

Example Walkthrough

Consider the following binary tree:

        2
       / \
      3   1
     / \   \
    3   1   1
  

All root-to-leaf paths:

  • 2 → 3 → 3: [2, 3, 3]
  • 2 → 3 → 1: [2, 3, 1]
  • 2 → 1 → 1: [2, 1, 1]

Let's check each path:

  • [2, 3, 3]: Counts: 2 appears once, 3 appears twice. Only one digit (2) appears odd times. This is pseudo-palindromic.
  • [2, 3, 1]: Counts: 2, 3, and 1 each appear once. Three digits appear odd times. Not pseudo-palindromic.
  • [2, 1, 1]: Counts: 2 appears once, 1 appears twice. Only one digit (2) appears odd times. This is pseudo-palindromic.
So the answer is 2.

The bitmask at each leaf would look like this:

  • [2, 3, 3]: path = 0b100 (2) → 0b1100 (3) → 0b100 (3 again toggles bit 3 off). Only one bit set.
  • [2, 3, 1]: path = 0b100 (2) → 0b1100 (3) → 0b1110 (1). Three bits set.
  • [2, 1, 1]: path = 0b100 (2) → 0b110 (1) → 0b100 (1 again toggles bit 1 off). Only one bit set.

Time and Space Complexity

Brute-force Approach:

  • Time Complexity: O(N * D), where N is the number of nodes and D is the maximum depth of the tree (since each path is stored and checked).
  • Space Complexity: O(D) for the recursion stack and O(D) for storing each path.
Optimized Bitmask Approach:
  • Time Complexity: O(N), since each node is visited once and bit operations are constant time.
  • Space Complexity: O(H), where H is the height of the tree (recursion stack). No extra space per path.

The optimized approach is much more efficient, especially for large trees, as it avoids storing full paths or counts and leverages fast bitwise operations.

Summary

The key insight for this problem is recognizing that the palindrome property can be efficiently checked using bit manipulation, by tracking the parity of digit counts along each root-to-leaf path. Using DFS with a bitmask, we can traverse the tree in linear time and space, checking the pseudo-palindromic property at each leaf in constant time. This approach is both elegant and practical for large binary trees.