Skip to content

Commit bbf9560

Browse files
committed
feat: complete minimum_height_trees
1 parent b8343b2 commit bbf9560

File tree

10 files changed

+236
-16
lines changed

10 files changed

+236
-16
lines changed

.templates/leetcode/json/clone_graph.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@
3737
"test_methods": [
3838
{
3939
"name": "test_clone_graph",
40-
"parametrize": "adj_list, expected_adj_list",
41-
"parametrize_typed": "adj_list: list[list[int]], expected_adj_list: list[list[int]]",
42-
"test_cases": "[([[2,4],[1,3],[2,4],[1,3]], [[2,4],[1,3],[2,4],[1,3]]), ([[]], [[]]), ([], [])]",
43-
"body": "node = GraphNode.from_adjacency_list(adj_list)\nexpected = GraphNode.from_adjacency_list(expected_adj_list)\nresult = self.solution.clone_graph(node)\nassert GraphNode.to_adjacency_list(result) == GraphNode.to_adjacency_list(expected)"
40+
"parametrize": "adj_list",
41+
"parametrize_typed": "adj_list: list[list[int]]",
42+
"test_cases": "[[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []]",
43+
"body": "node = GraphNode.from_adjacency_list(adj_list)\nresult = self.solution.clone_graph(node)\nassert result.is_clone(node) if result else node is None"
4444
}
4545
],
4646
"playground_imports": "from solution import Solution\n\nfrom leetcode_py import GraphNode",
47-
"playground_test_case": "# Example test case\nadj_list = [[2,4],[1,3],[2,4],[1,3]]\nnode = GraphNode.from_adjacency_list(adj_list)\nexpected_adj_list = [[2,4],[1,3],[2,4],[1,3]]",
48-
"playground_execution": "result = Solution().clone_graph(node)\nGraphNode.to_adjacency_list(result)",
49-
"playground_assertion": "assert GraphNode.to_adjacency_list(result) == expected_adj_list"
47+
"playground_test_case": "# Example test case\nadj_list = [[2,4],[1,3],[2,4],[1,3]]\nnode = GraphNode.from_adjacency_list(adj_list)",
48+
"playground_execution": "result = Solution().clone_graph(node)\nresult",
49+
"playground_assertion": "assert result.is_clone(node) if result else node is None"
5050
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"problem_name": "minimum_height_trees",
3+
"solution_class_name": "Solution",
4+
"problem_number": "310",
5+
"problem_title": "Minimum Height Trees",
6+
"difficulty": "Medium",
7+
"topics": "Depth-First Search, Breadth-First Search, Graph, Topological Sort",
8+
"tags": [],
9+
"readme_description": "A tree is an undirected graph in which any two vertices are connected by *exactly* one path. In other words, any connected graph without simple cycles is a tree.\n\nGiven a tree of `n` nodes labelled from `0` to `n - 1`, and an array of `n - 1` `edges` where `edges[i] = [ai, bi]` indicates that there is an undirected edge between the two nodes `ai` and `bi` in the tree, you can choose any node of the tree as the root. When you select a node `x` as the root, the result tree has height `h`. Among all possible rooted trees, those with minimum height (i.e. `min(h)`) are called **minimum height trees** (MHTs).\n\nReturn *a list of all **MHTs'** root labels*. You can return the answer in **any order**.\n\nThe **height** of a rooted tree is the number of edges on the longest downward path between the root and a leaf.",
10+
"readme_examples": [
11+
{
12+
"content": "<img alt=\"\" src=\"https://assets.leetcode.com/uploads/2020/09/01/e1.jpg\" style=\"width: 800px; height: 213px;\" />\n\n```\nInput: n = 4, edges = [[1,0],[1,2],[1,3]]\nOutput: [1]\nExplanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT.\n```"
13+
},
14+
{
15+
"content": "<img alt=\"\" src=\"https://assets.leetcode.com/uploads/2020/09/01/e2.jpg\" style=\"width: 800px; height: 321px;\" />\n\n```\nInput: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]\nOutput: [3,4]\n```"
16+
}
17+
],
18+
"readme_constraints": "- `1 <= n <= 2 * 10^4`\n- `edges.length == n - 1`\n- `0 <= ai, bi < n`\n- `ai != bi`\n- All the pairs `(ai, bi)` are distinct.\n- The given input is **guaranteed** to be a tree and there will be **no repeated** edges.",
19+
"readme_additional": "",
20+
"solution_imports": "",
21+
"solution_methods": [
22+
{
23+
"name": "find_min_height_trees",
24+
"parameters": "n: int, edges: list[list[int]]",
25+
"return_type": "list[int]",
26+
"dummy_return": "[]"
27+
}
28+
],
29+
"test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
30+
"test_class_name": "MinimumHeightTrees",
31+
"test_helper_methods": [
32+
{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
33+
],
34+
"test_methods": [
35+
{
36+
"name": "test_find_min_height_trees",
37+
"parametrize": "n, edges, expected",
38+
"parametrize_typed": "n: int, edges: list[list[int]], expected: list[int]",
39+
"test_cases": "[(4, [[1,0],[1,2],[1,3]], [1]), (6, [[3,0],[3,1],[3,2],[3,4],[5,4]], [3,4]), (1, [], [0])]",
40+
"body": "result = self.solution.find_min_height_trees(n, edges)\nassert sorted(result) == sorted(expected)"
41+
}
42+
],
43+
"playground_imports": "from solution import Solution",
44+
"playground_test_case": "# Example test case\nn = 4\nedges = [[1,0],[1,2],[1,3]]\nexpected = [1]",
45+
"playground_execution": "result = Solution().find_min_height_trees(n, edges)\nresult",
46+
"playground_assertion": "assert sorted(result) == sorted(expected)"
47+
}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PYTHON_VERSION = 3.13
2-
PROBLEM ?= clone_graph
2+
PROBLEM ?= minimum_height_trees
33
FORCE ?= 0
44

55
sync_submodules:

leetcode/clone_graph/playground.ipynb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,7 @@
181181
"metadata": {},
182182
"outputs": [],
183183
"source": [
184-
"if result is None:\n",
185-
" assert node is None\n",
186-
"else:\n",
187-
" assert result.is_clone(node)"
184+
"assert result.is_clone(node) if result else node is None"
188185
]
189186
}
190187
],

leetcode/clone_graph/tests.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,4 @@ def test_clone_graph(
2222
node = GraphNode.from_adjacency_list(adj_list)
2323
result = solution.clone_graph(node)
2424

25-
if result is None:
26-
assert node is None
27-
else:
28-
assert result.is_clone(node)
25+
assert result.is_clone(node) if result else node is None
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Minimum Height Trees
2+
3+
**Difficulty:** Medium
4+
**Topics:** Depth-First Search, Breadth-First Search, Graph, Topological Sort
5+
**Tags:**
6+
7+
**LeetCode:** [Problem 310](https://leetcode.com/problems/minimum-height-trees/description/)
8+
9+
## Problem Description
10+
11+
A tree is an undirected graph in which any two vertices are connected by _exactly_ one path. In other words, any connected graph without simple cycles is a tree.
12+
13+
Given a tree of `n` nodes labelled from `0` to `n - 1`, and an array of `n - 1` `edges` where `edges[i] = [ai, bi]` indicates that there is an undirected edge between the two nodes `ai` and `bi` in the tree, you can choose any node of the tree as the root. When you select a node `x` as the root, the result tree has height `h`. Among all possible rooted trees, those with minimum height (i.e. `min(h)`) are called **minimum height trees** (MHTs).
14+
15+
Return _a list of all **MHTs'** root labels_. You can return the answer in **any order**.
16+
17+
The **height** of a rooted tree is the number of edges on the longest downward path between the root and a leaf.
18+
19+
## Examples
20+
21+
### Example 1:
22+
23+
<img alt="" src="https://assets.leetcode.com/uploads/2020/09/01/e1.jpg" style="width: 800px; height: 213px;" />
24+
25+
```
26+
Input: n = 4, edges = [[1,0],[1,2],[1,3]]
27+
Output: [1]
28+
Explanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT.
29+
```
30+
31+
### Example 2:
32+
33+
<img alt="" src="https://assets.leetcode.com/uploads/2020/09/01/e2.jpg" style="width: 800px; height: 321px;" />
34+
35+
```
36+
Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
37+
Output: [3,4]
38+
```
39+
40+
## Constraints
41+
42+
- `1 <= n <= 2 * 10^4`
43+
- `edges.length == n - 1`
44+
- `0 <= ai, bi < n`
45+
- `ai != bi`
46+
- All the pairs `(ai, bi)` are distinct.
47+
- The given input is **guaranteed** to be a tree and there will be **no repeated** edges.

leetcode/minimum_height_trees/__init__.py

Whitespace-only changes.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "imports",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from solution import Solution"
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": 2,
16+
"id": "setup",
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"# Example test case\n",
21+
"n = 4\n",
22+
"edges = [[1, 0], [1, 2], [1, 3]]\n",
23+
"expected = [1]"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": 3,
29+
"id": "execute",
30+
"metadata": {},
31+
"outputs": [
32+
{
33+
"data": {
34+
"text/plain": [
35+
"[1]"
36+
]
37+
},
38+
"execution_count": 3,
39+
"metadata": {},
40+
"output_type": "execute_result"
41+
}
42+
],
43+
"source": [
44+
"result = Solution().find_min_height_trees(n, edges)\n",
45+
"result"
46+
]
47+
},
48+
{
49+
"cell_type": "code",
50+
"execution_count": 4,
51+
"id": "test",
52+
"metadata": {},
53+
"outputs": [],
54+
"source": [
55+
"assert sorted(result) == sorted(expected)"
56+
]
57+
}
58+
],
59+
"metadata": {
60+
"kernelspec": {
61+
"display_name": "leetcode-py-py3.13",
62+
"language": "python",
63+
"name": "python3"
64+
},
65+
"language_info": {
66+
"codemirror_mode": {
67+
"name": "ipython",
68+
"version": 3
69+
},
70+
"file_extension": ".py",
71+
"mimetype": "text/x-python",
72+
"name": "python",
73+
"nbconvert_exporter": "python",
74+
"pygments_lexer": "ipython3",
75+
"version": "3.13.7"
76+
}
77+
},
78+
"nbformat": 4,
79+
"nbformat_minor": 5
80+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from collections import defaultdict, deque
2+
3+
4+
class Solution:
5+
# Time: O(V)
6+
# Space: O(V)
7+
def find_min_height_trees(self, n: int, edges: list[list[int]]) -> list[int]:
8+
if n == 1:
9+
return [0]
10+
11+
graph = defaultdict(set)
12+
for u, v in edges:
13+
graph[u].add(v)
14+
graph[v].add(u)
15+
16+
leaves = deque([i for i in range(n) if len(graph[i]) == 1])
17+
18+
remaining = n
19+
while remaining > 2:
20+
size = len(leaves)
21+
remaining -= size
22+
for _ in range(size):
23+
leaf = leaves.popleft()
24+
neighbor = graph[leaf].pop()
25+
graph[neighbor].remove(leaf)
26+
if len(graph[neighbor]) == 1:
27+
leaves.append(neighbor)
28+
29+
return list(leaves)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pytest
2+
3+
from leetcode_py.test_utils import logged_test
4+
5+
from .solution import Solution
6+
7+
8+
class TestMinimumHeightTrees:
9+
def setup_method(self):
10+
self.solution = Solution()
11+
12+
@pytest.mark.parametrize(
13+
"n, edges, expected",
14+
[
15+
(4, [[1, 0], [1, 2], [1, 3]], [1]),
16+
(6, [[3, 0], [3, 1], [3, 2], [3, 4], [5, 4]], [3, 4]),
17+
(1, [], [0]),
18+
],
19+
)
20+
@logged_test
21+
def test_find_min_height_trees(self, n: int, edges: list[list[int]], expected: list[int]):
22+
result = self.solution.find_min_height_trees(n, edges)
23+
assert sorted(result) == sorted(expected)

0 commit comments

Comments
 (0)