홈

현이의 개발 이야기

도넛과 막대 그래프 - Lv 2 문제 풀이

프로그래머스 > Java로 코테풀기2024. 08. 23. 15:36

구현 중심의 문제로, 제시된 조건만 파악하면 쉽게 풀리는 문제였다.
문제에서 파악해야 하는 핵심 조건이 무엇이었는지를 알아보고, 이를 해결하기 위한 구현 과정을 살펴보자.

문제 풀이

문제에서는 도넛 묘양, 막대 모양, 8자 모양의 세 종류의 그래프를 제시합니다. 그리고 이 세 그래프에 속하지 않은 하나의 노드가 추가되어 모든 그래프를 연결합니다.

그래프 별 노드의 특징

먼저, 각 그래프에 속한 노드와 새로 추가한 노드의 특징을 살펴봅시다.

도넛 모양 그래프의 노드

도넛 모양 그래프의 모든 노드는 다음의 특징을 갖습니다.

막대 모양 그래프의 노드

막대 그래프의 모든 노드는 다음의 특징을 갖습니다.

8자 모양 그래프의 노드

8자 모양 그래프의 모든 노드는 다음의 특징을 갖습니다.

새로 삽입된 노드

새로 삽입된 노드는 두 개 이상의 그래프를 연결하기 때문에 다음의 특징을 갖습니다.

삽입된 노드 찾아 그래프 분리하기

위에서 살펴 본 노드의 특징을 활용하여 삽입된 노드를 찾아, 그래프들을 종류별로 분리할 수 있습니다.

삽입된 노드 찾기

새로 삽입된 노드의 특징 중 하나인 들어오는 간선이 없는 노드를 생각해 봅시다.
막대 모양 그래프의 시작 노드가 이러한 특징을 가질 수 있습니다. 그러나 나가는 간선이 두 개 이상이어야 한다는 점을 생각하면 이를 만족하는 노드는 새로 삽입된 노드 뿐입니다.
따라서 모든 노드를 순회하며, 들어오는 간선이 없고, 나가는 간선이 두 개 이상인 노드를 찾는다면, 해당 노드가 삽입된 노드임을 알 수 있습니다.

그래프 분리하기

삽입된 노드를 찾은 후에, 해당 노드와 연결된 간선들을 모두 끊으면 종류별로 명확히 분리된 그래프를 얻을 수 있습니다.
위 그림에서 알 수 있듯이, 삽입된 노드를 제거하면 왼쪽부터 8자 모양 그래프, 막대 모양 그래프, 8자 모양 그래프임을 쉽게 알 수 있습니다.

그래프 구분하기

그래프를 분리했으니 이제 각 그래프가 어떤 모양인지를 검사해야 합니다.
이 과정은 앞서 살펴본 노드의 특징을 활용하면 쉽게 할 수 있습니다.
임의의 한 노드로부터 출발하여 간선을 따라가다가,
여기서 주의할 점은, 8자 모양 그래프에 대한 검사를 도넛 모양 그래프 검사보다 우선 해야 한다는 것입니다.
8자 모양 그래프도 한 노드로부터 간선을 따라가다보면 자기 자신이 나오게 됩니다. 하지만 그 이전에 8자 모양 그래프의 중심 노드를 거쳐야 하기 때문에 이를 우선 검사하여 8자 모양 그래프와 도넛 모양 그래프를 구분합니다.

코드

이를 코드로 구현하면 다음과 같습니다.
import java.util.*;
import java.util.function.Function;

class Solution {
    enum NodeType {
        DONUT,
        LINEAR,
        EIGHT,
    }
    
    static class Node {
        List<Node> outs = new ArrayList<>();
        List<Node> ins = new ArrayList<>();
        NodeType type;
    }
    
    private static Map<Integer, Node> constructNodes(int[][] edges) {
        Map<Integer, Node> nodes = new HashMap<>();
        
        for (int[] edge : edges) {
            int u = edge[0];
            int v = edge[1];
            
            if (!nodes.containsKey(u)) nodes.put(u, new Node());
            if (!nodes.containsKey(v)) nodes.put(v, new Node());
            
            Node from = nodes.get(u);
            Node to = nodes.get(v);
            
            from.outs.add(to);
            to.ins.add(from);
        }
        
        return nodes;
    }
    
    private static int findInsertedNodeKey(Map<Integer, Node> nodes) {
        for (int key : nodes.keySet()) {
            Node node = nodes.get(key);
            if (node.ins.size() == 0 && node.outs.size() >= 2) {
                return key;
            }
        }
        
        // Unreachable
        return -1;
    }
    
    private static int removeInsertedNode(Map<Integer, Node> nodes) {
        int insertedKey = findInsertedNodeKey(nodes);
        Node inserted = nodes.remove(insertedKey);
        
        for (Node node : inserted.ins) {
            node.outs.remove(inserted);
        }
        for (Node node : inserted.outs) {
            node.ins.remove(inserted);
        }
        return insertedKey;
    }
    
    private static Node getUnvisitedNext(Node node, List<Node> direction) {
        for (Node next : direction) {
            if (next.type == null) return next;
        }
        return null;
    }
    
    private static void mark(Node node, NodeType type, Function<Node, List<Node>> getDirection) {
        do {
            node.type = type;
            node = getUnvisitedNext(node, getDirection.apply(node));
        } while (node != null);
    }
    
    private static NodeType check(Node node) {
        Node initial = node;
        while (true) {
            if (node.outs.size() == 0) {
                mark(node, NodeType.LINEAR, n -> n.ins);
                return NodeType.LINEAR;
            } else if (node.ins.size() == 2 && node.outs.size() == 2) {
                mark(getUnvisitedNext(node, node.outs), NodeType.EIGHT, n -> n.outs);
                return NodeType.EIGHT;
            } 
            
            node = getUnvisitedNext(node, node.outs);
            if (node == null) {
                // Unreachable
                break;
            }
            
            if (node == initial) {
                mark(node, NodeType.DONUT, n -> n.outs);
                return NodeType.DONUT;
            }   
        };
        return null;
    }
    
    public int[] solution(int[][] edges) {
        Map<Integer, Node> nodes = constructNodes(edges);
        int insertedKey = removeInsertedNode(nodes);
        
        int donut = 0;
        int linear = 0;
        int eight = 0;
        
        for (Node node : nodes.values()) {
            if (node.type != null) continue;
            
            switch (check(node)) {
                case LINEAR:
                    linear += 1;
                    break;
                case EIGHT:
                    eight += 1;
                    break;
                case DONUT:
                    donut += 1;
                    break;
            }
        }
        
        return new int[] { insertedKey, donut, linear, eight };
    }
}
댓글 0

로그인이 필요합니다.
로그인