0

I am trying to write a solution for Leet Code problem 261. Graph Valid Tree:

Given n nodes labeled from 0 to n-1 and a list of undirected edges (each edge is a pair of nodes), write a function to check whether these edges make up a valid tree.

Example 1:

Input: n = 5, and edges = [[0,1], [0,2], [0,3], [1,4]]
Output: true

Example 2:

Input: n = 5, and edges = [[0,1], [1,2], [2,3], [1,3], [1,4]]
Output: false

Here is my solution thus far. I believe that the goal is to detect cycles in the tree. I use dfs to do this.

class Node:
    def __init__(self, val):
        self.val = val
        self.outgoing = []

class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        
        visited = {}
        for pre, end in edges:
            if pre not in visited:
                "we add a new node to the visited set"
                visited[pre] = Node(pre)
            if end not in visited:
                visited[end] = Node(end)
            "We append it to the list"
            visited[pre].outgoing.append(visited[end])  
            
        def dfs(current, dvisit = set()):
            if current.val in dvisit:
                print("is the condition happening here")
                return True
            dvisit.add(current.val)
            
            for nodes in current.outgoing:
                dfs(nodes, dvisit)  
            return False
        
        mdict = set()     
        for key in visited.keys():
            mdict.clear()
            if dfs(visited[key], mdict) == True:
                return False
        return True

It fails this test n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]] It is supposed to return false but it returns true.

I placed some print statements in my dfs helper function and it does seem to be hitting the case where dfs is supposed to return true. However for some reason, the case in my for loop does not hit in the end, which causes the entire problem to return true for some reason. Can I receive some guidance on how I can modify this?

Yilmaz
  • 35,338
  • 10
  • 157
  • 202

2 Answers2

0

A few issues:

  • The given graph is undirected, so edges should be added in both directions when the tree data structure is built. Without doing this, you might miss cycles.

  • Once edges are made undirected, the algorithm should not travel back along the edge it just came from. For this purpose keep track of the parent node that the traversal just came from.

  • In dfs the returned value from the recursive call is ignored. It should not: when the returned value indicates there is a cycle, the loop should be exited and the same indication should be returned to the caller.

  • The main loop should not clear mdict. In fact, if after the first call to dfs, that loop finds another node that has not been visited, then this means the graph is not a tree: in a tree every pair of nodes is connected. No second call of dfs needs to be made from the main code, which means the main code does not need a loop. It can just call dfs on any node and then check that all nodes were visited by that call.

  • The function could do a preliminary "sanity" check, since a tree always has one less edge than it has vertices. If that is not true, then there is no need to continue: it is not a tree.

  • One boundary check could be made: when n is 1, and thus there are no edges, then there is nothing to call dfs on. In that case we can just return True, as this is a valid boundary case.

So a correction could look like this:

class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        if n != len(edges) + 1:  # Quick sanity check
            return False
        if n == 1:  # Boundary case
            return True

        visited = {}
        for pre, end in edges:
            if pre not in visited:
                visited[pre] = Node(pre)
            if end not in visited:
                visited[end] = Node(end)
            visited[pre].outgoing.append(visited[end])  
            visited[end].outgoing.append(visited[pre])  # It's undirected  
            
        def dfs(parent, current, dvisit):
            if current.val in dvisit:
                return True  # Cycle detected!
            dvisit.add(current.val)
            
            for node in current.outgoing:
                # Avoid going back along the same edge, and
                #     check the returned boolean!
                if node is not parent and dfs(current, node, dvisit):  
                    return True  # Quit as soon as cycle is found
            return False
        
        mdict = set()
        # Start in any node:
        if dfs(None, visited[pre], mdict):
            return False  # Cycle detected!
        # After one DFS traversal there should not be any node that has not been visited
        return len(mdict) == n
trincot
  • 317,000
  • 35
  • 244
  • 286
0

A tree is a special undirected graph. It satisfies two properties

  • It is connected
  • It has no cycle.

No cycle can be expressed as NumberOfNodes ==NumberOfEdges+1.

Based on this, given edges:

1- Create the graph

2- then traverse the graph and store the nodes in a set

3- Finally check if two conditions above are met

class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        from collections import defaultdict
        graph = defaultdict(list)
        for src, dest in edges:
            graph[src].append(dest)
            graph[dest].append(src)
        visited = set()
        def dfs(root):
            visited.add(root)
            for node in graph[root]:
                if node in visited:
                    # if you already visited before, means you alredy run dfs so do not run dfs again
                    continue
                dfs(node)
        dfs(0)
        # this shows we have no cycle and connected
        return len(visited) == n and len(edges)+1 == n

This question is locked in leetcode but you can test it here for now:

https://www.lintcode.com/problem/178/description

Yilmaz
  • 35,338
  • 10
  • 157
  • 202