Problem

A rule looks like this:

A NE B

This means this means point A is located northeast of point B.

A SW C

means that point A is southwest of C.

Given a list of rules, check if the sum of the rules validate.

Examples

Example 1:

Input: rules = [
	"A N B",
	"B NE C",
	"C N A"
]
Output: false
Explanation: does not validate, since `A` cannot be both north and south of `C`.

Example 2:

Input: rules = [
	"A NW B",
	"A N B"
]
Output: true

Solution

Method 1 - BFS

To solve this problem, we need to deduce and verify relative positions of points based on given directional rules. Here’s the approach you can use:

  1. Graph Representation of Directions:
    • Treat this as a graph problem where each point is a vertex and each directional rule is a directed edge with specific constraints.
  2. Define Constraints:
    • Use vectors to represent each directional constraint. For example:
      • N (North) can be represented as (0, 1).
      • NE (Northeast) can be represented as (1, 1).
      • Similarly, define vectors for other directions NWSSWEWSE.
  3. Translation to Graph Problem:
    • Initialize a dictionary for each point to store its relative position.
    • Use vectors to establish constraints and check for consistency.
  4. Cycle Detection:
    • Check for any contradictory cycles within the graph of constraints.

Code

Java
public class Solution {

    // Method to check if the given rules are valid
    public boolean isValid(String[] rules) {
        Map<String, List<String[]>> graph = new HashMap<>();
        Map<String, int[]> position = new HashMap<>();

        // Build the graph
        for (String rule : rules) {
            String[] parts = rule.split(" ");
            String a = parts[0];
            String direction = parts[1];
            String b = parts[2];

            graph.putIfAbsent(a, new ArrayList<>());
            graph.putIfAbsent(b, new ArrayList<>());
            graph.get(a).add(new String[] {b, direction});
            graph.get(b).add(new String[] {a, oppositeDirection(direction)});
        }

        // Use BFS to propagate positions
        for (String node : graph.keySet()) {
            if (!position.containsKey(node)) {
                if (!bfs(node, graph, position)) {
                    return false;
                }
            }
        }

        return true;
    }
    
    // Method to convert direction to a vector
    private int[] directionToVector(String direction) {
        switch (direction) {
            case "N":
                return new int[] {0, 1};
            case "S":
                return new int[] {0, -1};
            case "E":
                return new int[] {1, 0};
            case "W":
                return new int[] {-1, 0};
            case "NE":
                return new int[] {1, 1};
            case "NW":
                return new int[] {-1, 1};
            case "SE":
                return new int[] {1, -1};
            case "SW":
                return new int[] {-1, -1};
            default:
                throw new IllegalArgumentException(
                    "Invalid direction: " + direction);
        }
    }

    // BFS Helper Method
    private boolean bfs(String start, Map<String, List<String[]>> graph,
        Map<String, int[]> position) {
        Queue<String> queue = new LinkedList<>();
        queue.add(start);
        position.put(start, new int[] {0, 0});

        while (!queue.isEmpty()) {
            String current = queue.poll();
            int[] currentPos = position.get(current);

            for (String[] neighbor : graph.get(current)) {
                String neighborNode = neighbor[0];
                int[] move = directionToVector(neighbor[1]);
                int[] newPos = new int[] {
                    currentPos[0] + move[0], currentPos[1] + move[1]};

                if (!position.containsKey(neighborNode)) {
                    position.put(neighborNode, newPos);
                    queue.add(neighborNode);
                } else {
                    int[] existingPos = position.get(neighborNode);
                    if (existingPos[0] != newPos[0]
                        || existingPos[1] != newPos[1]) {
                        return false;
                    }
                }
            }
        }

        return true;
    }

    // Method to get opposite direction
    private String oppositeDirection(String direction) {
        switch (direction) {
            case "N":
                return "S";
            case "S":
                return "N";
            case "E":
                return "W";
            case "W":
                return "E";
            case "NE":
                return "SW";
            case "NW":
                return "SE";
            case "SE":
                return "NW";
            case "SW":
                return "NE";
            default:
                throw new IllegalArgumentException(
                    "Invalid direction: " + direction);
        }
    }

    public static void main(String[] args) {
        String[] rules1 = {"A N B", "B NE C", "C N A"};
        System.out.println(isValid(rules1)); // Output: false

        String[] rules2 = {"A NW B", "A N B"};
        System.out.println(isValid(rules2)); // Output: true
    }
}
Python
def direction_to_vector(direction):
    directions = {
        "N": (0, 1),
        "S": (0, -1),
        "E": (1, 0),
        "W": (-1, 0),
        "NE": (1, 1),
        "NW": (-1, 1),
        "SE": (1, -1),
        "SW": (-1, -1),
    }
    return directions[direction]


def is_valid(rules):
    graph = defaultdict(list)
    vectors = defaultdict(lambda: [0, 0])

    # Create the graph and directional vectors
    for rule in rules:
        a, direction, b = rule.split()
        vec = direction_to_vector(direction)

        graph[a].append((b, vec))
        graph[b].append((a, (-vec[0], -vec[1])))

    # Initialize visited dictionary
    visited = {}

    def bfs(start):
        queue = deque([(start, (0, 0))])
        visited[start] = (0, 0)

        while queue:
            current, pos = queue.popleft()

            for neighbor, move in graph[current]:
                new_pos = (pos[0] + move[0], pos[1] + move[1])

                if neighbor in visited:
                    if visited[neighbor] != new_pos:
                        return False
                else:
                    visited[neighbor] = new_pos
                    queue.append((neighbor, new_pos))
        return True

    # Check each component of the graph
    for node in graph:
        if node not in visited:
            if not bfs(node):
                return False
    return True


# Example usage:
rules1 = ["A N B", "B NE C", "C N A"]
print(is_valid(rules1))  # Output: False

rules2 = ["A NW B", "A N B"]
print(is_valid(rules2))  # Output: True

Complexity

  • ⏰ Time complexity: O(V + E) where V is the number of vertices (points) and E is the number of edges (rules).
  • 🧺 Space complexity: O(V + E),  for storing the graph and position mappings.