Facebook Pixel

1042. Flower Planting With No Adjacent

Problem Description

You are given n gardens numbered from 1 to n, and an array paths where each element paths[i] = [xi, yi] represents a bidirectional path connecting garden xi with garden yi.

Your task is to plant flowers in each garden following these rules:

  • You have 4 types of flowers available (numbered 1, 2, 3, and 4)
  • Each garden must be planted with exactly one type of flower
  • Any two gardens that are directly connected by a path must have different flower types
  • Each garden has at most 3 paths connecting to other gardens

You need to return an array answer of length n, where answer[i] represents the flower type planted in garden (i+1). The problem guarantees that a valid solution always exists.

For example, if you have 3 gardens with paths [[1,2], [2,3]], a valid answer could be [1, 2, 1] since:

  • Garden 1 has flower type 1
  • Garden 2 has flower type 2 (different from garden 1, which it's connected to)
  • Garden 3 has flower type 1 (different from garden 2, which it's connected to)

The key insight is that since each garden has at most 3 connections and we have 4 flower types available, we can always find a valid flower type for each garden by checking which types are already used by its neighbors.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The problem explicitly involves gardens (nodes) and paths between them (edges). We have n gardens and paths array that describes bidirectional connections between gardens.

Is it a tree?

  • No: A tree has exactly n-1 edges for n nodes and no cycles. Our graph can have up to 3 connections per garden and may contain cycles. The problem doesn't specify it's a tree structure.

Is the problem related to directed acyclic graphs?

  • No: The paths are bidirectional (undirected graph), not directed. Also, cycles may exist in the graph.

Is the problem related to shortest paths?

  • No: We're not finding shortest paths between gardens. We're assigning flower types to ensure adjacent gardens have different flowers.

Does the problem involve connectivity?

  • No: We're not checking if gardens are connected or finding connected components. We're coloring the graph nodes.

Does the problem have small constraints?

  • Yes: Each garden has at most 3 paths (degree ≀ 3), and we only have 4 flower types. This is a small, bounded constraint.

DFS/backtracking?

  • Yes: With small constraints and the need to assign values (flower types) while checking neighbors, DFS/backtracking is appropriate for exploring and assigning flower types to each garden.

Conclusion: The flowchart suggests using DFS/backtracking for this graph coloring problem with small constraints. The solution uses a greedy approach that works due to the constraint that each node has at most 3 neighbors and we have 4 colors available.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight comes from understanding the constraint that each garden has at most 3 paths connecting to other gardens, while we have 4 flower types available. This is the crucial observation that makes the problem solvable with a simple greedy approach.

Think about it this way: when we're trying to assign a flower type to a garden, in the worst case, it could be connected to 3 other gardens. If all 3 neighboring gardens have different flower types, they can only use up 3 out of our 4 available flower types. This means we'll always have at least 1 flower type remaining that we can use for the current garden.

This is why we don't need complex backtracking or checking for conflicts later - we can simply process each garden one by one and greedily assign the first available flower type that hasn't been used by its neighbors.

The approach mirrors the classic graph coloring problem, but with a guarantee that we'll never get stuck. For each garden:

  1. Look at all the gardens connected to it
  2. See what flower types those connected gardens are using
  3. Pick the first flower type from {1, 2, 3, 4} that isn't being used by any neighbor

Since we process gardens sequentially and each garden can "block" at most 3 flower types (from its at most 3 neighbors), we're guaranteed to always find a valid flower type from our set of 4.

This is why the solution doesn't need to backtrack or reconsider previous choices - the mathematical relationship between maximum degree (3) and number of colors (4) ensures that a greedy assignment will always work.

Learn more about Depth-First Search, Breadth-First Search and Graph patterns.

Solution Approach

The implementation follows a straightforward greedy coloring strategy:

Step 1: Build the Graph First, we construct an adjacency list representation of the garden connections. We use a defaultdict(list) to store the graph where g[x] contains all gardens adjacent to garden x. Since the problem uses 1-based indexing for gardens but Python uses 0-based indexing, we convert by subtracting 1 from each garden number:

g = defaultdict(list)
for x, y in paths:
    x, y = x - 1, y - 1  # Convert to 0-based indexing
    g[x].append(y)
    g[y].append(x)  # Add both directions since paths are bidirectional

Step 2: Initialize the Result Array We create an array ans of size n initialized with zeros to store the flower type for each garden:

ans = [0] * n

Step 3: Assign Flower Types to Each Garden For each garden x from 0 to n-1, we:

  1. Find used flower types: Create a set containing the flower types already assigned to all neighboring gardens:

    used = {ans[y] for y in g[x]}

    This set comprehension looks at each neighbor y of garden x and collects their assigned flower types. If a neighbor hasn't been assigned yet (ans[y] = 0), it gets added to the set but doesn't affect our choice since we only check for flower types 1-4.

  2. Choose the first available flower type: Iterate through flower types 1 to 4 and pick the first one not in the used set:

    for c in range(1, 5):
        if c not in used:
            ans[x] = c
            break

Step 4: Return the Result After processing all gardens, return the ans array containing the flower type assignments.

The time complexity is O(n + m) where n is the number of gardens and m is the number of paths, as we process each garden once and examine each edge twice (once from each endpoint). The space complexity is O(n + m) for storing the graph and the result array.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with 4 gardens and paths [[1,2], [3,4], [4,1], [2,3]].

Initial Setup:

  • Gardens: 1, 2, 3, 4
  • Paths create connections: 1↔2, 3↔4, 4↔1, 2↔3
  • Result array: [0, 0, 0, 0] (initially no flowers assigned)

Step 1: Build the adjacency list (using 0-based indexing):

  • Garden 0 (originally 1): connected to [1, 3]
  • Garden 1 (originally 2): connected to [0, 2]
  • Garden 2 (originally 3): connected to [3, 1]
  • Garden 3 (originally 4): connected to [2, 0]

Step 2: Process each garden greedily

Garden 0:

  • Check neighbors: gardens 1 and 3
  • Used flower types: {0, 0} = {0} (both neighbors unassigned)
  • Available types: 1, 2, 3, 4 all available
  • Assign: flower type 1
  • Result: [1, 0, 0, 0]

Garden 1:

  • Check neighbors: gardens 0 and 2
  • Used flower types: {1, 0} = {1, 0} (garden 0 has type 1)
  • First available type not in {1, 0}: type 2
  • Assign: flower type 2
  • Result: [1, 2, 0, 0]

Garden 2:

  • Check neighbors: gardens 3 and 1
  • Used flower types: {0, 2} = {0, 2} (garden 1 has type 2)
  • First available type not in {0, 2}: type 1
  • Assign: flower type 1
  • Result: [1, 2, 1, 0]

Garden 3:

  • Check neighbors: gardens 2 and 0
  • Used flower types: {1, 1} = {1} (both have type 1)
  • First available type not in {1}: type 2
  • Assign: flower type 2
  • Result: [1, 2, 1, 2]

Verification:

  • Garden 1 (type 1) ↔ Garden 2 (type 2) βœ“ Different
  • Garden 3 (type 1) ↔ Garden 4 (type 2) βœ“ Different
  • Garden 4 (type 2) ↔ Garden 1 (type 1) βœ“ Different
  • Garden 2 (type 2) ↔ Garden 3 (type 1) βœ“ Different

The greedy approach successfully assigns flower types without conflicts. Note how we never needed to backtrack - at each step, we always had at least one flower type available because each garden has at most 3 neighbors but we have 4 flower types to choose from.

Solution Implementation

1from collections import defaultdict
2from typing import List
3
4class Solution:
5    def gardenNoAdj(self, n: int, paths: List[List[int]]) -> List[int]:
6        # Build adjacency list for the graph
7        # Key: garden index (0-based), Value: list of adjacent garden indices
8        adjacency_graph = defaultdict(list)
9      
10        # Convert paths to 0-based indexing and build bidirectional edges
11        for garden1, garden2 in paths:
12            # Convert from 1-based to 0-based indexing
13            garden1_idx = garden1 - 1
14            garden2_idx = garden2 - 1
15          
16            # Add bidirectional edges
17            adjacency_graph[garden1_idx].append(garden2_idx)
18            adjacency_graph[garden2_idx].append(garden1_idx)
19      
20        # Initialize result array to store flower type for each garden
21        # 0 means no flower type assigned yet
22        flower_assignments = [0] * n
23      
24        # Process each garden one by one
25        for current_garden in range(n):
26            # Collect flower types already used by neighboring gardens
27            used_flower_types = {flower_assignments[neighbor] 
28                                for neighbor in adjacency_graph[current_garden]}
29          
30            # Try assigning flower types 1 through 4
31            for flower_type in range(1, 5):
32                # If this flower type is not used by any neighbor
33                if flower_type not in used_flower_types:
34                    # Assign this flower type to current garden
35                    flower_assignments[current_garden] = flower_type
36                    break  # Move to next garden
37      
38        return flower_assignments
39
1class Solution {
2    public int[] gardenNoAdj(int n, int[][] paths) {
3        // Create adjacency list to represent the graph
4        // Each garden (node) will store its connected gardens
5        List<Integer>[] adjacencyList = new List[n];
6        Arrays.setAll(adjacencyList, index -> new ArrayList<>());
7      
8        // Build the undirected graph from the paths
9        // Convert 1-indexed gardens to 0-indexed for array access
10        for (int[] path : paths) {
11            int garden1 = path[0] - 1;
12            int garden2 = path[1] - 1;
13            adjacencyList[garden1].add(garden2);
14            adjacencyList[garden2].add(garden1);
15        }
16      
17        // Array to store the flower type (1-4) for each garden
18        int[] flowerAssignment = new int[n];
19      
20        // Boolean array to track which flower types are used by neighbors
21        // Index 0 is unused, indices 1-4 represent flower types
22        boolean[] usedFlowers = new boolean[5];
23      
24        // Process each garden to assign a flower type
25        for (int currentGarden = 0; currentGarden < n; currentGarden++) {
26            // Reset the used flowers array for current garden
27            Arrays.fill(usedFlowers, false);
28          
29            // Mark flower types that are already used by neighboring gardens
30            for (int neighbor : adjacencyList[currentGarden]) {
31                usedFlowers[flowerAssignment[neighbor]] = true;
32            }
33          
34            // Find the first available flower type (1-4) and assign it
35            for (int flowerType = 1; flowerType <= 4; flowerType++) {
36                if (!usedFlowers[flowerType]) {
37                    flowerAssignment[currentGarden] = flowerType;
38                    break;
39                }
40            }
41        }
42      
43        return flowerAssignment;
44    }
45}
46
1class Solution {
2public:
3    vector<int> gardenNoAdj(int n, vector<vector<int>>& paths) {
4        // Build adjacency list for the garden graph
5        // Each garden can have at most 3 neighbors
6        vector<vector<int>> adjacencyList(n);
7      
8        // Convert 1-indexed paths to 0-indexed and build bidirectional edges
9        for (const auto& path : paths) {
10            int garden1 = path[0] - 1;  // Convert to 0-indexed
11            int garden2 = path[1] - 1;  // Convert to 0-indexed
12          
13            // Add bidirectional edges
14            adjacencyList[garden1].push_back(garden2);
15            adjacencyList[garden2].push_back(garden1);
16        }
17      
18        // Result array to store flower type (1-4) for each garden
19        vector<int> flowerAssignment(n, 0);
20      
21        // Array to track which flower types are used by neighbors
22        bool usedFlowers[5];  // Index 0 unused, indices 1-4 for flower types
23      
24        // Assign flower type to each garden
25        for (int currentGarden = 0; currentGarden < n; ++currentGarden) {
26            // Reset the used flowers array for current garden
27            memset(usedFlowers, false, sizeof(usedFlowers));
28          
29            // Mark flower types already used by neighboring gardens
30            for (int neighbor : adjacencyList[currentGarden]) {
31                usedFlowers[flowerAssignment[neighbor]] = true;
32            }
33          
34            // Find the first available flower type (1-4) and assign it
35            for (int flowerType = 1; flowerType <= 4; ++flowerType) {
36                if (!usedFlowers[flowerType]) {
37                    flowerAssignment[currentGarden] = flowerType;
38                    break;
39                }
40            }
41        }
42      
43        return flowerAssignment;
44    }
45};
46
1/**
2 * Assigns flower types to gardens such that no two adjacent gardens have the same flower type.
3 * Each garden can have one of 4 flower types (represented as 1, 2, 3, or 4).
4 * 
5 * @param n - Number of gardens (labeled from 1 to n)
6 * @param paths - Array of paths where each path [x, y] connects garden x to garden y
7 * @returns Array where ans[i] represents the flower type assigned to garden i+1
8 */
9function gardenNoAdj(n: number, paths: number[][]): number[] {
10    // Build adjacency list representation of the graph
11    // Each index represents a garden, containing an array of its neighboring gardens
12    const adjacencyList: number[][] = Array.from({ length: n }, () => []);
13  
14    // Populate the adjacency list (converting 1-indexed to 0-indexed)
15    for (const [garden1, garden2] of paths) {
16        adjacencyList[garden1 - 1].push(garden2 - 1);
17        adjacencyList[garden2 - 1].push(garden1 - 1);
18    }
19  
20    // Initialize result array to store flower type for each garden
21    const flowerAssignments: number[] = Array(n).fill(0);
22  
23    // Process each garden sequentially
24    for (let currentGarden = 0; currentGarden < n; ++currentGarden) {
25        // Track which flower types are already used by neighboring gardens
26        // Index 0 is unused, indices 1-4 represent flower types 1-4
27        const usedFlowerTypes: boolean[] = Array(5).fill(false);
28      
29        // Mark flower types used by all neighboring gardens
30        for (const neighborGarden of adjacencyList[currentGarden]) {
31            usedFlowerTypes[flowerAssignments[neighborGarden]] = true;
32        }
33      
34        // Assign the first available flower type (1-4) to current garden
35        for (let flowerType = 1; flowerType < 5; ++flowerType) {
36            if (!usedFlowerTypes[flowerType]) {
37                flowerAssignments[currentGarden] = flowerType;
38                break;
39            }
40        }
41    }
42  
43    return flowerAssignments;
44}
45

Time and Space Complexity

Time Complexity: O(n + m)

The algorithm consists of two main parts:

  • Building the adjacency list: We iterate through all m paths once, and for each path, we perform constant-time operations (appending to lists). This takes O(m) time.
  • Assigning colors to gardens: We iterate through all n gardens. For each garden x, we:
    • Create a set of used colors by checking all neighbors of x. Since each garden can have at most 3 neighbors (as we have 4 colors available and the graph is guaranteed to be colorable), this takes O(1) time.
    • Find an unused color from the 4 available colors, which takes O(1) time in the worst case.

Therefore, the total time complexity is O(m) + O(n) = O(n + m).

Space Complexity: O(n + m)

The space usage includes:

  • The adjacency list g: In the worst case, it stores all edges twice (once for each direction), requiring O(m) space.
  • The answer array ans: Stores one color for each of the n gardens, requiring O(n) space.
  • The used set: Contains at most 3 elements at any time (bounded by the number of neighbors), which is O(1) space.

Therefore, the total space complexity is O(n + m).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Index Confusion Between 1-Based and 0-Based

The most common mistake is forgetting to convert between 1-based garden numbering (used in the problem) and 0-based array indexing (used in Python).

Incorrect approach:

# Forgetting to convert indices
for x, y in paths:
    g[x].append(y)  # Wrong! x and y are 1-based
    g[y].append(x)

Why it fails: This would try to access g[n] when the maximum garden number is n, causing an IndexError or incorrect adjacency relationships.

Solution: Always convert to 0-based indexing immediately:

for x, y in paths:
    x, y = x - 1, y - 1  # Convert to 0-based first
    g[x].append(y)
    g[y].append(x)

2. Forgetting Bidirectional Paths

Another common mistake is treating paths as unidirectional, only adding one direction to the adjacency list.

Incorrect approach:

for x, y in paths:
    x, y = x - 1, y - 1
    g[x].append(y)  # Only adding one direction

Why it fails: Garden y wouldn't know about its connection to garden x, leading to potential conflicts where both gardens might get assigned the same flower type.

Solution: Always add both directions:

g[x].append(y)
g[y].append(x)  # Don't forget the reverse edge

3. Not Handling Isolated Gardens

If a garden has no paths connecting to it, it might not appear in the adjacency list at all when using a regular dictionary.

Incorrect approach:

g = {}  # Regular dictionary
for current_garden in range(n):
    used = {ans[y] for y in g[current_garden]}  # KeyError if garden has no paths

Why it fails: Accessing g[current_garden] for an isolated garden would raise a KeyError.

Solution: Use defaultdict(list) which returns an empty list for missing keys:

g = defaultdict(list)
# Now g[isolated_garden] returns [] instead of raising KeyError

4. Assuming Gardens Must Be Processed in Order

While the greedy approach works when processing gardens sequentially, some might mistakenly think they need to process gardens in a specific order (like BFS/DFS traversal).

Why it's unnecessary: Since each garden has at most 3 connections and we have 4 flower types, we're guaranteed to always have at least one available flower type regardless of processing order. The sequential approach (0 to n-1) is sufficient and simpler.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used in a depth first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More