Python BFS, No TLE, Easy to Understand
This solution would get TLE if we used visited set instead of discovered because the queue will contain many duplicates if keep enqueing nodes until they\'ve be
This solution would get TLE if we used `visited` set instead of `discovered` because the queue will contain many duplicates if keep enqueing nodes until they\'ve been explored.\n\n```\nBLOCKED = 0\n\n\nclass DisjointNodesError(Exception):\n    pass\n\n\nclass Solution:\n    """\n    we want to minimize the number of steps: ie path length\n    we need to cut down trees in order from shortest to tallest\n\n    find shortest path from start (0, 0) to shortest tree\n    then find shortest path to each tree in sorted order\n    summing paths the entire time\n\t\n\tall moves: north east west south  => cost 1\n    unweighted graph, bfs will give us the shortest path between any two points\n\t\n    if we cannot get to a tree because the trees are disjoint return -1\n    we shouldn\'t need to check beforehand whether or not trees are disjoint because we will find out during breadth first search\n\n    first, sort trees by height so we know order to target them in => N logN\n    then do bfs between each tree in sorted order => bfs O(n) * # trees n\n    O(n^2)\n    """\n\n    def __init__(self):\n        self.grid = [[]]\n        self.left_bound = -1\n        self.top_bound = -1\n        self.bottom_bound = 0\n        self.right_bound = 0\n\n    def get_neighbors(self, node):\n        r, c = node\n        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]\n        neighbors = []\n        for dr, dc in directions:\n            if self.top_bound < r + dr < self.bottom_bound and self.left_bound < c + dc < self.right_bound:\n                if self.grid[r + dr][c + dc] != BLOCKED:\n                    neighbors.append((r + dr, c + dc))\n        return neighbors\n\n    def bfs(self, start, end):\n        discovered = set()\n        queue = deque([(start, 0)])\n        while queue:\n            current, distance = queue.popleft()\n            if current == end:\n                return distance\n            for neighbor in self.get_neighbors(current):\n                if neighbor not in discovered:\n                    queue.append((neighbor, distance + 1))\n                    # use discovered instead of visited so we don\'t add duplicate vertices to queue\n                    discovered.add(neighbor)\n        raise DisjointNodesError()\n\n    def cutOffTree(self, forest: List[List[int]]) -> int:\n        self.grid = forest\n        self.bottom_bound = len(forest)\n        self.right_bound = len(forest[0])\n        trees = []\n        # gather trees\n        for r, row in enumerate(forest):\n            for c, height in enumerate(row):\n                if height > 1:\n                    trees.append((height, r, c))\n        # sort trees\n        trees.sort(key=lambda x: x[0])\n        num_steps = 0\n        # find shortest path between trees, sum path length\n        start = (0, 0)\n        for idx in range(0, len(trees)):\n            height, r, c = trees[idx]\n            next_tree = (r, c)\n            try:\n                distance = self.bfs(start, next_tree)\n            except DisjointNodesError:\n                return -1\n            num_steps += distance\n            start = next_tree\n        return num_steps\n\t\t```
['Breadth-First Search', 'Python']