LeetCode 1042 - Flower Planting With No Adjacent
The problem presents n gardens, labeled from 1 to n, and a list of bidirectional paths connecting pairs of gardens. Each garden must be planted with one of four types of flowers (1 through 4).
Difficulty: 🟡 Medium
Topics: Depth-First Search, Breadth-First Search, Graph Theory
Solution
Problem Understanding
The problem presents n gardens, labeled from 1 to n, and a list of bidirectional paths connecting pairs of gardens. Each garden must be planted with one of four types of flowers (1 through 4). The constraint is that no two gardens connected by a path can have the same type of flower. The task is to return any valid assignment of flower types for all gardens.
The input consists of an integer n representing the number of gardens and an array paths representing garden connections. Each element in paths is a pair [xi, yi] indicating a two-way path. The output should be an array answer of length n, where answer[i] represents the flower type planted in garden i+1.
Constraints provide important guarantees: each garden has at most 3 paths. This implies that with 4 flower types available, it is always possible to choose a flower type not used by neighboring gardens, guaranteeing a solution exists. The input size can be as large as n = 10^4 and up to 2 * 10^4 paths, meaning a naive brute-force enumeration of all flower assignments would be too slow.
Important edge cases include:
- A garden with no paths, which can safely take any flower.
- A garden connected to the maximum of 3 neighbors, where careful selection is required.
- Fully connected subsets (triangles) of gardens, which are still solvable due to the four flower types.
Approaches
Brute Force
A naive brute-force approach would attempt to assign flowers recursively to each garden, backtracking whenever a neighbor has the same flower. While correct, this method is exponential in time complexity (O(4^n)), because every garden has 4 choices and the search space grows combinatorially. This is impractical for n up to 10^4.
Optimal Approach
The key observation is that each garden has at most 3 neighbors and there are 4 flower types, which guarantees at least one available flower for every garden. We can adopt a greedy approach:
- Represent the gardens and paths as an adjacency list.
- Iterate through each garden in order.
- Check the flower types assigned to its neighbors.
- Assign the smallest-numbered flower not used by neighbors.
This approach ensures a valid assignment in a single pass and leverages the problem constraint that a solution always exists.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(4^n) | O(n) | Tries every combination recursively; impractical |
| Greedy Assignment (Optimal) | O(n + e) | O(n + e) | Uses adjacency list and assigns flowers in a single pass |
Algorithm Walkthrough
- Initialize an adjacency list
graphmapping each garden to a list of its neighboring gardens. - For each path
[u, v]inpaths, addvtograph[u]andutograph[v]. - Initialize an array
answerof lengthnwith zeros, representing unassigned flowers. - Iterate over each garden from
1ton. - For the current garden, create a set of flower types already used by its neighbors.
- Assign the smallest flower type from
1to4that is not in the set of neighbors' flowers. - Continue until all gardens have an assigned flower.
- Return
answer.
Why it works: Since each garden has at most 3 neighbors and there are 4 flower types, there is always at least one flower type not used by neighbors. By greedily assigning the smallest available flower to each garden, the algorithm ensures all constraints are satisfied.
Python Solution
from typing import List
from collections import defaultdict
class Solution:
def gardenNoAdj(self, n: int, paths: List[List[int]]) -> List[int]:
graph = defaultdict(list)
for u, v in paths:
graph[u].append(v)
graph[v].append(u)
answer = [0] * n
for garden in range(1, n + 1):
used = {answer[neighbor - 1] for neighbor in graph[garden] if answer[neighbor - 1] != 0}
for flower in range(1, 5):
if flower not in used:
answer[garden - 1] = flower
break
return answer
Explanation: We first build a graph using a defaultdict to store neighbors. Then, for each garden, we track flower types already used by neighbors in a set. Iterating over flower types from 1 to 4, we assign the first available flower to the garden. This ensures no two adjacent gardens share a flower.
Go Solution
func gardenNoAdj(n int, paths [][]int) []int {
graph := make([][]int, n+1)
for _, path := range paths {
u, v := path[0], path[1]
graph[u] = append(graph[u], v)
graph[v] = append(graph[v], u)
}
answer := make([]int, n)
for garden := 1; garden <= n; garden++ {
used := make(map[int]bool)
for _, neighbor := range graph[garden] {
if answer[neighbor-1] != 0 {
used[answer[neighbor-1]] = true
}
}
for flower := 1; flower <= 4; flower++ {
if !used[flower] {
answer[garden-1] = flower
break
}
}
}
return answer
}
Go-specific notes: We use slices for adjacency lists and a map to track used flower types. Arrays are 0-indexed, while garden labels are 1-indexed, so we adjust indices appropriately. The logic mirrors the Python version closely.
Worked Examples
Example 1: n = 3, paths = [[1,2],[2,3],[3,1]]
| Garden | Neighbors | Used Flowers | Assigned Flower |
|---|---|---|---|
| 1 | 2, 3 | {} | 1 |
| 2 | 1, 3 | {1} | 2 |
| 3 | 1, 2 | {1, 2} | 3 |
Output: [1, 2, 3]
Example 2: n = 4, paths = [[1,2],[3,4]]
| Garden | Neighbors | Used Flowers | Assigned Flower |
|---|---|---|---|
| 1 | 2 | {} | 1 |
| 2 | 1 | {1} | 2 |
| 3 | 4 | {} | 1 |
| 4 | 3 | {1} | 2 |
Output: [1, 2, 1, 2]
Example 3: n = 4, paths = [[1,2],[2,3],[3,4],[4,1],[1,3],[2,4]]
| Garden | Neighbors | Used Flowers | Assigned Flower |
|---|---|---|---|
| 1 | 2, 4, 3 | {} | 1 |
| 2 | 1, 3, 4 | {1} | 2 |
| 3 | 2, 4, 1 | {1,2} | 3 |
| 4 | 3, 1, 2 | {1,2,3} | 4 |
Output: [1, 2, 3, 4]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n + e) | Building the adjacency list takes O(e). Iterating over n gardens and their neighbors is O(n + e). |
| Space | O(n + e) | Adjacency list uses O(n + e), and the answer array uses O(n). |
The complexity is efficient for the problem constraints (n <= 10^4, paths <= 2*10^4).
Test Cases
# Basic examples
assert Solution().gardenNoAdj(3, [[1,2],[2,3],[3,1]]) in ([1,2,3], [1,2,4], [1,4,2], [3,2,1])
assert Solution().gardenNoAdj(4, [[1,2],[3,4]]) in ([1,2,1,2], [2,1,2,1])
assert Solution().gardenNoAdj(4, [[1,2],[2,3],[3,4],[4,1],[1,3],[2,4]]) == [1,2,3,4]
# Edge case: no paths
assert Solution().gardenNoAdj(5, []) == [1,1,1,1,1]
# Maximum neighbors (3)
assert Solution().gardenNoAdj(4, [[1,2],[1,3],[1,4]]) in ([1,2,3,4], [1,3,2,4], [1,2,4,3])
# Linear chain
assert Solution().gardenNoAdj(5, [[1,2],[2,3],[3,4],[4,5]]) in ([1,2,1,2,1], [1,