Skip to main content

Command Palette

Search for a command to run...

Solving Leetcode Interviews in Seconds with AI: Count Paths That Can Form a Palindrome in a Tree

Updated
4 min read

Introduction

In this blog post, we will explore how to solve the LeetCode problem "2791" using AI. LeetCode is a popular platform for preparing for coding interviews, and with the help of AI tools like Chatmagic, we can generate solutions quickly and efficiently - helping you pass the interviews and get the job offer without having to study for months.

Problem Statement

You are given a tree (i.e. a connected, undirected graph that has no cycles) rooted at node 0 consisting of n nodes numbered from 0 to n - 1. The tree is represented by a 0-indexed array parent of size n, where parent[i] is the parent of node i. Since node 0 is the root, parent[0] == -1. You are also given a string s of length n, where s[i] is the character assigned to the edge between i and parent[i]. s[0] can be ignored. Return the number of pairs of nodes (u, v) such that u < v and the characters assigned to edges on the path from u to v can be rearranged to form a palindrome. A string is a palindrome when it reads the same backwards as forwards. Example 1: Input: parent = [-1,0,0,1,1,2], s = "acaabc" Output: 8 Explanation: The valid pairs are: - All the pairs (0,1), (0,2), (1,3), (1,4) and (2,5) result in one character which is always a palindrome. - The pair (2,3) result in the string "aca" which is a palindrome. - The pair (1,5) result in the string "cac" which is a palindrome. - The pair (3,5) result in the string "acac" which can be rearranged into the palindrome "acca". Example 2: Input: parent = [-1,0,0,0,0], s = "aaaaa" Output: 10 Explanation: Any pair of nodes (u,v) where u < v is valid. Constraints: n == parent.length == s.length 1 <= n <= 105 0 <= parent[i] <= n - 1 for all i >= 1 parent[0] == -1 parent represents a valid tree. s consists of only lowercase English letters.

Explanation

Here's a breakdown of the approach, followed by the Python code:

  • Represent Path with Bitmask: Each path from the root to a node can be represented by a bitmask. The i-th bit is set if the character 'a' + i appears an odd number of times along the path. A path between two nodes is a palindrome if the bitwise XOR of their path bitmasks has at most one bit set (meaning at most one character appears an odd number of times in the combined path).
  • DFS with Bitmask: Perform a Depth-First Search (DFS) starting from the root (node 0). Keep track of the bitmask for the path from the root to the current node.
  • Count Valid Pairs: For each node, count the number of nodes in its subtree that form a valid palindrome path when combined with the path to the current node. Use a dictionary to store the counts of each bitmask encountered during the DFS.

  • Runtime Complexity: O(N), where N is the number of nodes in the tree. Storage Complexity: O(N) in the worst case due to the depth of the recursion and the size of the count dictionary.

Code

    def count_palindrome_pairs(parent, s):
    n = len(parent)
    adj = [[] for _ in range(n)]
    for i in range(1, n):
        adj[parent[i]].append(i)

    count = {}
    result = 0

    def dfs(node, mask):
        nonlocal result

        # Update mask with the character on the edge to the current node
        char_index = ord(s[node]) - ord('a') if node != 0 else -1 #s[0] is dummy
        if char_index != -1:
            mask ^= (1 << char_index)

        # Increment count for the current mask
        if mask not in count:
            count[mask] = 0
        count[mask] += 1

        # Check for valid pairs in the subtree of the current node
        result += count.get(mask, 0) -1 #avoid self count
        for i in range(26):
             result += count.get(mask ^ (1 << i), 0) if mask ^ (1 << i) in count else 0

        # Recursively process child nodes
        for neighbor in adj[node]:
            dfs(neighbor, mask)

        # Backtrack: Decrement count for the current mask
        count[mask] -= 1


    # Initialize count and perform DFS from the root
    count[0] = 1
    dfs(0, 0)

    #calculate number of paths between each pair of nodes

    node_paths = {} #node_id -> mask

    def get_paths(node, mask):
        nonlocal node_paths
        char_index = ord(s[node]) - ord('a') if node != 0 else -1 #s[0] is dummy
        if char_index != -1:
            mask ^= (1 << char_index)

        node_paths[node] = mask

        for child in adj[node]:
          get_paths(child, mask)

    get_paths(0,0)

    pairs = 0
    for u in range(n):
        for v in range(u+1,n):
            xor_mask = node_paths[u] ^ node_paths[v]

            if bin(xor_mask).count('1') <= 1:
                pairs +=1

    return pairs

More from this blog

C

Chatmagic blog

2894 posts