2101. Detonate the Maximum Bombs - Explanation

Problem Link



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Graph Representation - Understanding how to build an adjacency list from problem constraints
  • Depth-First Search (DFS) - Used to traverse the directed graph and count reachable bombs
  • Breadth-First Search (BFS) - Alternative traversal method for exploring the chain reaction level by level
  • Euclidean Distance - Understanding how to calculate distance between points and compare with radius

Intuition

When a bomb detonates, it triggers other bombs within its blast radius. This creates a chain reaction that can be modeled as a directed graph. Bomb A has an edge to bomb B if B is within A's blast radius. Note that this relationship is not symmetric since bombs have different radii. To find the maximum detonation, we try detonating each bomb and use DFS to count how many bombs explode in the chain reaction.

Algorithm

  1. Build an adjacency list representing which bombs can trigger which other bombs. For each pair of bombs, check if the distance between them is within the first bomb's radius (bomb A triggers B if distance squared <= radius of A squared).
  2. For each bomb as a starting point, run a dfs to count all reachable bombs.
  3. In the dfs, use a visited set to track which bombs have already detonated and recursively visit all bombs that can be triggered.
  4. Return the maximum count found across all starting bombs.
class Solution:
    def maximumDetonation(self, bombs: list[list[int]]) -> int:
        adj = [[] for _ in range(len(bombs))]

        for i in range(len(bombs)):
            x1, y1, r1 = bombs[i]
            for j in range(i + 1, len(bombs)):
                x2, y2, r2 = bombs[j]
                d = (x1 - x2) ** 2 + (y1 - y2) ** 2

                if d <= r1 ** 2:
                    adj[i].append(j)
                if d <= r2 ** 2:
                    adj[j].append(i)

        def dfs(i, visit):
            if i in visit:
                return 0
            visit.add(i)
            for nei in adj[i]:
                dfs(nei, visit)
            return len(visit)

        res = 0
        for i in range(len(bombs)):
            res = max(res, dfs(i, set()))
        return res

Time & Space Complexity

  • Time complexity: O(n3)O(n ^ 3)
  • Space complexity: O(n2)O(n ^ 2)

Intuition

The chain reaction of bomb detonations can also be explored level by level using BFS. Starting from an initial bomb, we explore all bombs it directly triggers, then all bombs those trigger, and so on. This approach naturally models the wave-like spread of explosions.

Algorithm

  1. Build the same adjacency list as in the dfs approach, where an edge from A to B means bomb A can trigger bomb B.
  2. For each bomb as a starting point, initialize a queue with that bomb and a visit array.
  3. Process bombs from the queue: for each bomb, add all unvisited bombs it can trigger to the queue and mark them visited.
  4. Count the total number of visited bombs and track the maximum across all starting points.
class Solution:
    def maximumDetonation(self, bombs: list[list[int]]) -> int:
        n = len(bombs)
        adj = [[] for _ in range(n)]

        for i in range(n):
            x1, y1, r1 = bombs[i]
            for j in range(i + 1, n):
                x2, y2, r2 = bombs[j]
                d = (x1 - x2) ** 2 + (y1 - y2) ** 2

                if d <= r1 ** 2:
                    adj[i].append(j)
                if d <= r2 ** 2:
                    adj[j].append(i)

        res = 0
        for i in range(n):
            q = deque([i])
            visit = [False] * n
            visit[i] = True
            count = 1
            while q:
                node = q.popleft()
                for nei in adj[node]:
                    if not visit[nei]:
                        visit[nei] = True
                        count += 1
                        q.append(nei)
            res = max(res, count)
        return res

Time & Space Complexity

  • Time complexity: O(n3)O(n ^ 3)
  • Space complexity: O(n2)O(n ^ 2)

3. Iterative DFS

Intuition

This is the same approach as recursive DFS but uses an explicit stack instead of the call stack. This avoids potential stack overflow issues for very large inputs and can be more efficient in some languages due to reduced function call overhead.

Algorithm

  1. Build the adjacency list the same way as before.
  2. For each bomb as a starting point, initialize a stack with that bomb and a visit array.
  3. Pop bombs from the stack: for each bomb, push all unvisited neighbors onto the stack and mark them visited.
  4. Count all visited bombs and track the maximum across all starting points.
class Solution:
    def maximumDetonation(self, bombs: list[list[int]]) -> int:
        n = len(bombs)
        adj = [[] for _ in range(n)]

        for i in range(n):
            x1, y1, r1 = bombs[i]
            for j in range(i + 1, n):
                x2, y2, r2 = bombs[j]
                d = (x1 - x2) ** 2 + (y1 - y2) ** 2

                if d <= r1 ** 2:
                    adj[i].append(j)
                if d <= r2 ** 2:
                    adj[j].append(i)

        res = 0
        for i in range(n):
            stack = [i]
            visit = [False] * n
            visit[i] = True
            count = 1

            while stack:
                node = stack.pop()
                for nei in adj[node]:
                    if not visit[nei]:
                        visit[nei] = True
                        count += 1
                        stack.append(nei)
            res = max(res, count)
        return res

Time & Space Complexity

  • Time complexity: O(n3)O(n ^ 3)
  • Space complexity: O(n2)O(n ^ 2)

Common Pitfalls

Treating the Graph as Undirected

A common mistake is assuming that if bomb A can trigger bomb B, then B can also trigger A. This is incorrect because bombs have different radii. The graph is directed: an edge from A to B exists only if B is within A's blast radius.

# Wrong: Adding bidirectional edges unconditionally
if d <= r1 ** 2:
    adj[i].append(j)
    adj[j].append(i)  # Incorrect!

# Correct: Check each direction separately
if d <= r1 ** 2:
    adj[i].append(j)
if d <= r2 ** 2:
    adj[j].append(i)

Integer Overflow When Computing Distance

When calculating the squared distance between bombs, the intermediate values can overflow 32-bit integers. Coordinates can be up to 10^5 and squaring the difference can exceed INT_MAX. Always use 64-bit integers for distance calculations.

// Wrong: Overflow possible
int d = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);

// Correct: Cast to long before multiplication
long d = (long)(x1 - x2) * (x1 - x2) + (long)(y1 - y2) * (y1 - y2);

Using Square Root for Distance Comparison

Computing the actual Euclidean distance using sqrt() introduces floating-point precision errors. Instead, compare squared distances directly to avoid precision issues.

# Wrong: Floating-point precision issues
import math
if math.sqrt((x1-x2)**2 + (y1-y2)**2) <= r1:
    adj[i].append(j)

# Correct: Compare squared values
if (x1-x2)**2 + (y1-y2)**2 <= r1**2:
    adj[i].append(j)