Skip to main content

Clone Graph

How to deep-copy a graph with cycles by keeping a clear mapping from old nodes to new ones.

Given a node in a connected undirected graph, return a deep copy of the graph.

Each node has:

  • val
  • neighbors

In the original problem, the input is usually a reference to node 1.

In the SeniorPath playground, to keep comparison deterministic, use an adjacency list:

  • adjList[i] represents the neighbors of node i + 1

And return a new adjacency list with the same structure.

Example:

Input:  adjList = [[2,4],[1,3],[2,4],[1,3]]
Output: [[2,4],[1,3],[2,4],[1,3]]

The output looks the same, but it must be an independent copy.

What to notice before coding

Common mistakes

Step 1 - Understand what deep copy means here

It is not enough to return a structure with the same values.

Deep copy means:

  • every node in the copy is new
  • connections in the copy point to nodes in the copy
  • nothing in the copy points back to the old graph

Step 2 - Start with the flattened playground version

In the playground, the input already comes as an adjacency list.

So the most direct copy is:

  • create a new array
  • copy each neighbor list

That solves the deterministic playground representation.

But it still does not teach the heart of the original problem.

Step 3 - See why the original problem is trickier

When the input is a node with references to neighbors:

  • the graph can contain cycles
  • you may see the same node many times

If every repeated visit creates a new node, the copy breaks.

Step 4 - Remember what was already cloned

The strong rule is:

  • if the old node already has a copy, reuse it
  • if it does not, create it and register it before exploring neighbors

That "register first" step is what prevents an infinite loop in DFS.

The interactive editor needs JavaScript. You can still read the prompt and copy the starter code below.

Starter code

export function cloneGraphFromAdjList(adjList: number[][]): number[][] {
  // return a deep copy of the adjacency list
  return []
}
solution.ts
Stuck? Hints available.

Haven't solved it yet?

Viewing the solution now may reduce learning. It's worth trying a bit more.

Open the reference solution

Without JavaScript, the reference solution is shown inline instead of in a dialog.

Solution

Final complexity

Time: O(V + E)

Space: O(V)

Solution 1 - Copy the playground adjacency list

Good baseline for the flattened and deterministic representation.

export function cloneGraphFromAdjList(adjList: number[][]): number[][] {
  return adjList.map((neighbors) => [...neighbors])
}

It works for the playground adaptation.

But it is not the core idea of the original problem, where the input is a graph of connected objects.

Solution 2 - Clone the real graph with an old -> new hash map and DFS

Now the faithful version uses graph nodes and memorizes the copies.

type GraphNode = {
  val: number
  neighbors: GraphNode[]
}

function buildGraph(adjList: number[][]): GraphNode | null {
  if (adjList.length === 0) {
    return null
  }

  const nodes = adjList.map((_, index) => ({ val: index + 1, neighbors: [] as GraphNode[] }))

  for (let index = 0; index < adjList.length; index += 1) {
    nodes[index].neighbors = adjList[index].map((neighborValue) => nodes[neighborValue - 1])
  }

  return nodes[0]
}

function cloneGraph(node: GraphNode | null): GraphNode | null {
  if (node === null) {
    return null
  }

  const clones = new Map<GraphNode, GraphNode>()

  function dfs(current: GraphNode): GraphNode {
    const existing = clones.get(current)

    if (existing) {
      return existing
    }

    const copy: GraphNode = { val: current.val, neighbors: [] }
    clones.set(current, copy)

    for (const neighbor of current.neighbors) {
      copy.neighbors.push(dfs(neighbor))
    }

    return copy
  }

  return dfs(node)
}

function toAdjList(node: GraphNode | null): number[][] {
  if (node === null) {
    return []
  }

  const result: number[][] = []
  const queue: GraphNode[] = [node]
  const seen = new Set<number>([node.val])

  while (queue.length > 0) {
    const current = queue.shift()!
    result[current.val - 1] = current.neighbors.map((neighbor) => neighbor.val)

    for (const neighbor of current.neighbors) {
      if (!seen.has(neighbor.val)) {
        seen.add(neighbor.val)
        queue.push(neighbor)
      }
    }
  }

  return result
}

export function cloneGraphFromAdjList(adjList: number[][]): number[][] {
  const root = buildGraph(adjList)
  const clonedRoot = cloneGraph(root)
  return toAdjList(clonedRoot)
}

The strong insight here is that the map is not “value to value”.

The map is:

  • old node to new node

What to say in the interview

A strong short explanation would be:

Deep-copying a graph with cycles requires memoization. I use a map from the original node to the cloned node. The first time I see a node, I create the copy and register it immediately. If I see that node again, I reuse the existing copy. That prevents both infinite loops and duplicated nodes.

That shows that you:

  • understood why cycles change the problem
  • separated old identity from new identity
  • justified why the map is necessary

When this pattern shows up again

This pattern comes back when you need to:

  • deep-copy a structure with references
  • traverse a cyclic graph
  • use DFS or BFS with memoization
  • maintain a mapping between old entities and new entities during a transformation

The point is not memorizing cloneGraph.

The point is learning when copying without memory breaks the structure identity.

Next challenge Subtree of Another Tree Previous challenge Longest Increasing Subsequence

Related challenges

Related articles