Skip to main content

Command Palette

Search for a command to run...

Solving Leetcode Interviews in Seconds with AI: Collect Coins in a Tree

Updated
5 min read

Introduction

In this blog post, we will explore how to solve the LeetCode problem "2603" 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

There exists an undirected and unrooted tree with n nodes indexed from 0 to n - 1. You are given an integer n and a 2D integer array edges of length n - 1, where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the tree. You are also given an array coins of size n where coins[i] can be either 0 or 1, where 1 indicates the presence of a coin in the vertex i. Initially, you choose to start at any vertex in the tree. Then, you can perform the following operations any number of times: Collect all the coins that are at a distance of at most 2 from the current vertex, or Move to any adjacent vertex in the tree. Find the minimum number of edges you need to go through to collect all the coins and go back to the initial vertex. Note that if you pass an edge several times, you need to count it into the answer several times. Example 1: Input: coins = [1,0,0,0,0,1], edges = [[0,1],[1,2],[2,3],[3,4],[4,5]] Output: 2 Explanation: Start at vertex 2, collect the coin at vertex 0, move to vertex 3, collect the coin at vertex 5 then move back to vertex 2. Example 2: Input: coins = [0,0,0,1,1,0,0,1], edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[5,6],[5,7]] Output: 2 Explanation: Start at vertex 0, collect the coins at vertices 4 and 3, move to vertex 2, collect the coin at vertex 7, then move back to vertex 0. Constraints: n == coins.length 1 <= n <= 3 * 104 0 <= coins[i] <= 1 edges.length == n - 1 edges[i].length == 2 0 <= ai, bi < n ai != bi edges represents a valid tree.

Explanation

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

  • Trim Leaves: Repeatedly remove leaves (nodes with degree 1) that have no coins and whose neighbors also have no coins within a distance of 2. This simplifies the tree without affecting the final answer.
  • Trim Tails: Remove "tails" from both ends of the remaining tree. A "tail" is a path of length at most 2 from a leaf to a node with degree > 2. This is because it's always optimal to collect the coins on such tails at the beginning or end of the traversal.
  • Calculate Diameter: Find the diameter of the resulting tree, which represents the longest path between any two nodes. The answer is twice the number of edges in this diameter.

  • Runtime Complexity: O(N), where N is the number of nodes.

  • Storage Complexity: O(N)

Code

    from collections import defaultdict, deque

def collect_coins(coins, edges):
    n = len(coins)
    adj = defaultdict(list)
    for u, v in edges:
        adj[u].append(v)
        adj[v].append(u)

    degrees = [len(adj[i]) for i in range(n)]
    has_coin = coins[:]
    q = deque()

    # Initial pruning of leaves with no coins
    for i in range(n):
        if degrees[i] == 1 and coins[i] == 0:
            q.append(i)

    while q:
        u = q.popleft()
        if degrees[u] <= 1:
            degrees[u] = 0
            for v in adj[u]:
                degrees[v] -= 1
                if degrees[v] == 1 and coins[v] == 0:
                    q.append(v)

    # Second pruning, considering neighbors' coins
    q = deque()
    for i in range(n):
        if degrees[i] > 0 and coins[i] == 0:
            has_neighbor_coin = False
            for neighbor in adj[i]:
                if degrees[neighbor] > 0 and coins[neighbor] == 1:
                    has_neighbor_coin = True
                    break
                if degrees[neighbor] > 0:
                    for neighbor2 in adj[neighbor]:
                        if degrees[neighbor2] > 0 and neighbor2 != i and coins[neighbor2] == 1:
                            has_neighbor_coin = True
                            break
                if has_neighbor_coin:
                    break
            if not has_neighbor_coin and degrees[i] == 1:
                q.append(i)

    while q:
        u = q.popleft()
        if degrees[u] <= 1:
            degrees[u] = 0
            for v in adj[u]:
                if degrees[v] > 0:
                    degrees[v] -= 1
                    has_neighbor_coin = False
                    if coins[v] == 1:
                        has_neighbor_coin = True
                    else:
                        for neighbor2 in adj[v]:
                            if degrees[neighbor2] > 0 and neighbor2 != u and coins[neighbor2] == 1:
                                has_neighbor_coin = True
                                break
                    if not has_neighbor_coin and degrees[v] == 1 :
                        q.append(v)

    # Find the "true" leaves that we need to consider
    leaves = []
    for i in range(n):
        if degrees[i] > 0 and degrees[i] == 1:
            leaves.append(i)

    #Trim tails
    for _ in range(2):
        q = deque()
        visited = [False] * n
        for leaf in leaves:
          q.append(leaf)
          visited[leaf] = True

        while q:
          u = q.popleft()

          if degrees[u] > 1:
              continue

          degrees[u] = 0

          for v in adj[u]:
            if degrees[v] > 0:
                degrees[v] -= 1
                leaves.remove(u)
                if degrees[v] == 1 and v in leaves:
                    continue
                else:
                    q.append(v)


    # Reconstruct the graph based on remaining nodes
    new_adj = defaultdict(list)
    remaining_nodes = [i for i in range(n) if degrees[i] > 0]

    for u, v in edges:
        if u in remaining_nodes and v in remaining_nodes:
            new_adj[u].append(v)
            new_adj[v].append(u)

    #Find Diameter
    def bfs(start_node):
        q = deque([(start_node, 0)])
        visited = {start_node}
        max_dist = 0
        farthest_node = start_node

        while q:
            node, dist = q.popleft()
            if dist > max_dist:
                max_dist = dist
                farthest_node = node

            for neighbor in new_adj[node]:
                if neighbor not in visited:
                    visited.add(neighbor)
                    q.append((neighbor, dist + 1))

        return farthest_node, max_dist

    if not remaining_nodes:
        return 0

    node1, _ = bfs(remaining_nodes[0])
    node2, diameter = bfs(node1)


    return 2 * diameter

More from this blog

C

Chatmagic blog

2894 posts

Solving Leetcode Interviews in Seconds with AI: Collect Coins in a Tree