Skip to content
JZLeetCode
Go back

LeetCode 1129 Shortest Path with Alternating Colors

Table of contents

Open Table of contents

Description

Question Links: LeetCode 1129 — Shortest Path with Alternating Colors

We have a directed graph whose edges are either red or blue. Starting from node 0, find the shortest path to every node such that consecutive edges always have different colors. Return -1 for a node that cannot be reached by a valid alternating path.

Constraints:

For example, if the only edges are red 0 -> 1 and red 1 -> 2, node 2 is unreachable: taking two red edges in a row is not allowed.

Idea

A regular BFS can mark a node visited once. That is not enough here: reaching the same node after a red edge leaves blue edges available, while reaching it after a blue edge leaves red edges available. So the search state must remember both the current node and the previous edge color.

For example, suppose the graph contains red 0 -> 1, blue 0 -> 1, red 1 -> 2, and blue 1 -> 3. There are two useful states for node 1:

(0, none) --red-->  (1, red)  --blue--> (3, blue)
     |
     +-----blue--> (1, blue) --red----> (2, red)

The first edge can have either color, so the initial BFS state uses a neutral color. From then on, each state follows only edges whose color differs from the previous one. We mark each (node, color) state when it enters the queue; this prevents cycles without discarding the other color-state for that node. Since the queue is breadth-first, the first distance recorded for each node is its shortest valid distance.

Let m be the total number of red and blue edges. There are at most two visited states per node, and every adjacency list is scanned only a constant number of times.

Complexity: Time O(n+m)O(n + m), Space O(n+m)O(n + m).

Java

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;

public final class ShortestPathAlternatingColors {
    private static final int RED = 0;
    private static final int BLUE = 1;
    private static final int NONE = -1;

    private ShortestPathAlternatingColors() {}

    public static int[] shortestAlternatingPaths(int n, int[][] redEdges, int[][] blueEdges) {
        List<List<int[]>> graph = new ArrayList<>();
        for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
        for (int[] edge : redEdges) graph.get(edge[0]).add(new int[]{edge[1], RED});
        for (int[] edge : blueEdges) graph.get(edge[0]).add(new int[]{edge[1], BLUE});

        int[] answer = new int[n];
        Arrays.fill(answer, -1);
        boolean[][] visited = new boolean[n][2];

        Queue<int[]> queue = new ArrayDeque<>();
        queue.offer(new int[]{0, NONE});
        int distance = 0;

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int[] current = queue.poll();
                int node = current[0];
                int previousColor = current[1];
                if (answer[node] == -1) answer[node] = distance;

                for (int[] next : graph.get(node)) {
                    int nextNode = next[0];
                    int nextColor = next[1];
                    if (nextColor != previousColor && !visited[nextNode][nextColor]) {
                        visited[nextNode][nextColor] = true;
                        queue.offer(new int[]{nextNode, nextColor});
                    }
                }
            }
            distance++;
        }
        return answer;
    }
}

Python

from collections import deque


class Solution:
    def shortestAlternatingPaths(
        self, n: int, redEdges: list[list[int]], blueEdges: list[list[int]]
    ) -> list[int]:
        graph = [[[] for _ in range(n)] for _ in range(2)]
        for color, edges in enumerate((redEdges, blueEdges)):
            for source, target in edges:
                graph[color][source].append(target)

        shortest = [-1] * n
        shortest[0] = 0
        visited = [[False, False] for _ in range(n)]
        queue = deque([(0, -1, 0)])

        while queue:
            node, last_color, distance = queue.popleft()
            for color in range(2):
                if color == last_color:
                    continue
                for neighbor in graph[color][node]:
                    if visited[neighbor][color]:
                        continue
                    visited[neighbor][color] = True
                    if shortest[neighbor] == -1:
                        shortest[neighbor] = distance + 1
                    queue.append((neighbor, color, distance + 1))

        return shortest

C++

#include <queue>
#include <tuple>
#include <vector>

using namespace std;

class Solution1129 {
public:
    vector<int> shortestAlternatingPaths(int n, vector<vector<int>>& redEdges, vector<vector<int>>& blueEdges) {
        constexpr int RED = 0;
        constexpr int BLUE = 1;
        constexpr int NONE = 2;

        vector<vector<vector<int>>> graph(2, vector<vector<int>>(n));
        for (const auto& edge : redEdges) graph[RED][edge[0]].push_back(edge[1]);
        for (const auto& edge : blueEdges) graph[BLUE][edge[0]].push_back(edge[1]);

        vector<int> answer(n, -1);
        vector<vector<bool>> visited(n, vector<bool>(2, false));
        queue<tuple<int, int, int>> q;
        q.push({0, NONE, 0});
        answer[0] = 0;

        while (!q.empty()) {
            auto [node, previousColor, distance] = q.front();
            q.pop();

            for (int color = RED; color <= BLUE; color++) {
                if (color == previousColor) continue;
                for (int next : graph[color][node]) {
                    if (visited[next][color]) continue;
                    visited[next][color] = true;
                    if (answer[next] == -1) answer[next] = distance + 1;
                    q.push({next, color, distance + 1});
                }
            }
        }

        return answer;
    }
};

Rust

use std::collections::VecDeque;

pub struct Solution;

impl Solution {
    pub fn shortest_alternating_paths(
        n: i32,
        red_edges: Vec<Vec<i32>>,
        blue_edges: Vec<Vec<i32>>,
    ) -> Vec<i32> {
        let n = n as usize;
        let mut graph = vec![vec![Vec::new(); n], vec![Vec::new(); n]];
        for edge in red_edges {
            graph[0][edge[0] as usize].push(edge[1] as usize);
        }
        for edge in blue_edges {
            graph[1][edge[0] as usize].push(edge[1] as usize);
        }

        let mut distances = vec![-1; n];
        distances[0] = 0;
        let mut visited = vec![vec![false; n], vec![false; n]];
        visited[0][0] = true;
        visited[1][0] = true;

        let mut queue = VecDeque::new();
        queue.push_back((0usize, 2usize, 0i32));

        while let Some((node, previous_color, distance)) = queue.pop_front() {
            for color in 0..2 {
                if color == previous_color {
                    continue;
                }
                for &next in &graph[color][node] {
                    if visited[color][next] {
                        continue;
                    }
                    visited[color][next] = true;
                    if distances[next] == -1 {
                        distances[next] = distance + 1;
                    }
                    queue.push_back((next, color, distance + 1));
                }
            }
        }

        distances
    }
}

References

Share this post on:

Previous Post
System Design - How the Transactional Outbox Works
Next Post
System Design - How Kubernetes Deployment Rolling Updates Work