프로그래머스발행일 2025. 7. 8.원본 https://blog.naver.com/jword_/223925986957 ↗

프로그래머스 - 도넛과 막대 그래프

프로그래머스 - 도넛과 막대 그래프 — #프로그래머스 #도넛과막대그래프 #개발자의도구들 사용된 언어: 코틀린, 혹은 파이썬 순서: 로직, 코드 구...

#프로그래머스#Naver Blog

#프로그래머스 #도넛과막대그래프 #개발자의도구들

​

​

  • 사용된 언어: 코틀린, 혹은 파이썬
  • 순서: 로직, 코드 구현, 코드 분석

시행착오

Programmers (KKAO WINTER INTERSHIP LV2) - 23%

이미지

[코딩테스트 연습 - 도넛과 막대 그래프
알고리즘 문제 연습 카카오톡 친구해요! 프로그래머스 교육 카카오 채널을 만들었어요. 여기를 눌러, 친구 추가를 해주세요. 신규 교육 과정 소식은 물론 다양한 이벤트 소식을 가장 먼저 알려드립니다.
school.programmers.co.kr](https://school.programmers.co.kr/learn/courses/30/lessons/258711)

이번 문제는 문제를 이해하고 분석하여 해결방안을 작성하는데 1시간 이상이 걸렸다. 구현도중 8자 그래프 판별이 어려워 도중에 포기했다. 시험에 나왔다면 무조건 틀렷을 것 같다.

javascript 코드 예제
                                    👓 내 생각
👉 각각의 점을 직접 방문하여 point와 edge 수를 계산하려고 함

👉 start point가 가장 나가는 정점이 많은 것으로 해석
❌ 보장되지 않음

👉 그래프 관리를 접점으로 함
ex)
graph:  {
    4: [11, 2, 8],
    1: [12], 8: [3],
    12: [7], 7: [11],
    9: [6], 10: [11],
    6: [10],
    3: [5, 8],
    11: [1, 9],
    5: [3]
}
❌ in bound가 없어서 알고리즘이 복잡해질 수 있음
  • 문제를 풀 때 임의로 이럴 것이다라고 생각하고 풀면 안된다는 것을 깨달았다. 정확하게 이치에 맞게 떨어지지 않으면 해당 공식을 사용하지 말자.

​

  • 기본적으로 메모리는 알고리즘의 복잡성을 줄이기 위해 사용된다. 메모리 설계만 잘해도 문제 푸는것에 많은 도움을 얻을 수 잇다.

🔍 출제자의 의도

with chat gpt o3

의도구체적 장치
① 차수(in/out)만으로 풀어라각 그래프 타입마다 특이 차수 정점이 단 1개씩 존재하도록 설계:• 막대 → out=0 꼬리• 8자 → out=2 & in=2 교차• 도넛 → 특이점 없음
② 선형(O(E + V)) 알고리즘 유도간선이 최대 100만 → DFS/BFS·SCC 같은 그래프 탐색은 비효율이라고 암시
③ 단순 카운팅 논리 구성stick = tail 수, eight = 교차 수, donut = outdeg\[start\] − stick − eight
④ “그래프 구조를 수치로 읽어내는” 감각 테스트차수 분포만 보고 전체 구조를 복원·분류하는 사고 연습
⑤ 실수 방지 포인트 삽입• self-loop(크기 1 도넛),• out=2지만 start가 아닌 교차점 구분,• 간선 정보에 등장하지 않는 정점(out=0) 누락 주의
  • 이번 문제는 간선의 수를 높게 설정하여 직접 탐색이 비효율적이라는 것을 간접적으로 암시하였다.
  • 그래프를 어떻게 구성해야하는지를 묻고 있다.
  • 각 그래프의 특징을 분석하여 포인트를 잡아내는 것을 물어본다.

​


요구하는 자료의 각 특징들을 정리하였다.

javascript 코드 예제
                                    1. 시작점: inbound = 0, only outbound >= 1
👉 문제에서 그래프의 합이 2이상이라 하였으므로 outbound >= 2로 설정

2. donut: inbound = 1, out bound 1

3. line: inbound = 1, out bound 0

3. eight: inbound = 2, outbound 2

그래프를 이루는 개별 노드가 모두 만족하는 특징이아닌, 그래프를 이룰때 만들어질 수 밖에 없는 노드의 특징을 정리한 것이다. 예를들어 eight의 inbound 2와 outbound 2는 그래프의 중간노드에 해당한다. 이렇게 각 그래프를 대표하는 특징을 가진 노드를 id 노드라고 하자.

​

하지면 여기서 몇가지 변수가 존재하는데, edge node의 존재 때문에 id 노드의 inbound가 1이 증가할 수 있다. 다른 그래프의 id 노드 역시 여러 변수가 생길 수 있다.

이미지

edge노드 때문에 inbound 노드로 그래프의 종류 파악이 까다롭다. inbound를 고려하지 앟고 각각의 특징을 추출하는 전략을 다시 선정해야한다.\\

javascript 코드 예제
                                    edge를 고려한 전략

1. 전체 그래프의 수 : edge 노드의 outbound 수
👉 우리는 두 개만 구하면 하나를 자동으로 알 수 있다.

2. eight id node: edge를 제외하면 유일하게 outbound가 2이다.

3. line id node: 유일하게 out bound가 존재하지 않는다.

위 조건으로 eight와 line 그래프의 수를 구하면 donut도 자동으로 구해진다.

​

⚠️ 추가적인 변수

이 문제의 변수는 inbound와 outbound가 모두 포함되지 않는 노드가 존재한다는 것이다. 이런 노드는 어떤 그래프에 속하지 않으므로 계산을 제외해줘야 한다.

​

javascript 코드 예제
                                    👉 line 그래프의 판별 조건이 변경

- edge가 아니다
- inbound가 반드시 하나 이상일 것 (edge노드에 의해 2가 될 수 있음)
- outbound는 0일 것

이렇게 두면 정확하게 line node를 계산할 수 있다.

정답 코드

javascript 코드 예제
                                    def solution(edges):
    out_graph = {}
    in_graph = {}

    if len(edges) == 1:
        return [1, 0, 1, 0]

    # find max point & set graph
    max_point = -999
    for u, v in edges:
        max_point = max(max_point, u, v)
        out_graph[u] = out_graph.get(u, 0) + 1
        in_graph[v] = in_graph.get(v, 0) + 1

    # find start
    start = -1
    for u, v in edges:
        outbound = out_graph.get(u, 0)
        inbound = in_graph.get(u, 0)
        if outbound >= 2 and inbound == 0:
            start = u

    total_graph = out_graph[start]

    stick = 0
    eight = 0
    # find stick and eight
    for i in range(1, max_point + 1):    # v가 체크가 안되네 ...
        if i == start:
            continue
        inbound = in_graph.get(i, 0)
        outbound = out_graph.get(i, 0)
        if outbound == 0 and inbound >= 1:
            stick += 1
            continue

        if outbound == 2:
            eight += 1
            continue

    donut = total_graph - (stick + eight)

    answer = [start, donut, stick, eight]

    return answer

번외) 사고 오류 코드

javascript 코드 예제
                                    def is_donut(key, graph):
    # 한번씩 거치며 자기 자신에게 돌아온다.
    # 정점 수와 간선 수가 일치한다.

    visited = set()

    point = 0
    edge = 0

    while 1:
        if key in visited:
            break

        visited.add(key)
        point += 1

        next = graph[key]
        if len(next) > 1:
            return [set(), False]
        key = next[0]
        edge += 1

    if point == edge:
        return [visited, True]

    # return [Set(정점들), True/False]

def is_direct(key, graph):
    # 간선이 아에 없거나
    # 간선이 하나만 존재하며 no-cycle
    # return [Set(정점들), True/False]

    visited = set()

    point = 0
    edge = 0

    while 1:
        if len(graph[key]) > 1:
            return [set(), False]

        if key not in graph: # 간선이 없으면 포함 x -> 끝점
            visited.add(key)
            point += 1
            break

        visited.add(key)
        point += 1

        key = graph[key][0]
        edge += 1

    if point - edge != 1:
        print("in direct, point - edge != 1")
        return [set(), False]

    return [visited, True]

def is_eight(key, graph):

    identifier = set()
    visited = {}

    point = 0
    edge = 0

    while 1:
        if visited[key] == 2:
            break

        if key not in visited:
            visited[key] = 1

        else:
            visited[key] += 1

        point += 1

        # check two path
        nexts = graph[key]

        if len(nexts) > 2:
            print("something wron ...")
            return [set(), False]

        if len(nexts) == 2:
            for nxt in nexts:
                if nxt == key:
                    continue
                key = nxt
            edge += 2
        else:
            key = nexts[0]
            edge += 1

def solution(edges):
    answer = []

    graph = {}

    for edge in edges:
        u, v = edge[0], edge[1]

        if u not in graph:
            graph[u] = [v]
            continue
        graph[u].append(v)

    # find start point
    max_len_key = 999
    max_len = -99
    for k in graph:
        len_values = len(graph[k])
        if len_values >= max_len:
            max_len = len_values
            max_len_key = k

    print("max_len_key", max_len_key)

    # find donut count
    donut_count = 0
    donuts = set()

    # find direct count
    direct_count = 0
    directs = set()

    # find eight
    eight_count = 0
    eights = set()

    print("graph: ", graph)

    return answer

# O(1) = 1,000,000
# [a b] : a -> b 간선
# 최소 그래프 합은 2이상
# answer = [생성한 정점의 번호, 도넛 모양 그래프 수, 막대모양 그래프수, 8자 모양 그래프 수 ]
  • 하나 하나 순회하면서 트리 모양을 분석하려고 했다.
  • 시간 복잡도를 생각했으면 이렇게 풀면 안된다는 것을 바로 파악가능