Solving Leetcode Interviews in Seconds with AI: Collect Coins in a Tree
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