프로그래머스 - 도넛과 막대 그래프
프로그래머스 - 도넛과 막대 그래프 — #프로그래머스 #도넛과막대그래프 #개발자의도구들 사용된 언어: 코틀린, 혹은 파이썬 순서: 로직, 코드 구...
#프로그래머스 #도넛과막대그래프 #개발자의도구들
- 사용된 언어: 코틀린, 혹은 파이썬
- 순서: 로직, 코드 구현, 코드 분석
시행착오
Programmers (KKAO WINTER INTERSHIP LV2) - 23%
[코딩테스트 연습 - 도넛과 막대 그래프
알고리즘 문제 연습 카카오톡 친구해요! 프로그래머스 교육 카카오 채널을 만들었어요. 여기를 눌러, 친구 추가를 해주세요. 신규 교육 과정 소식은 물론 다양한 이벤트 소식을 가장 먼저 알려드립니다.
school.programmers.co.kr](https://school.programmers.co.kr/learn/courses/30/lessons/258711)
이번 문제는 문제를 이해하고 분석하여 해결방안을 작성하는데 1시간 이상이 걸렸다. 구현도중 8자 그래프 판별이 어려워 도중에 포기했다. 시험에 나왔다면 무조건 틀렷을 것 같다.
👓 내 생각
👉 각각의 점을 직접 방문하여 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) 누락 주의 |
- 이번 문제는 간선의 수를 높게 설정하여 직접 탐색이 비효율적이라는 것을 간접적으로 암시하였다.
- 그래프를 어떻게 구성해야하는지를 묻고 있다.
- 각 그래프의 특징을 분석하여 포인트를 잡아내는 것을 물어본다.
요구하는 자료의 각 특징들을 정리하였다.
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를 고려하지 앟고 각각의 특징을 추출하는 전략을 다시 선정해야한다.\\
edge를 고려한 전략
1. 전체 그래프의 수 : edge 노드의 outbound 수
👉 우리는 두 개만 구하면 하나를 자동으로 알 수 있다.
2. eight id node: edge를 제외하면 유일하게 outbound가 2이다.
3. line id node: 유일하게 out bound가 존재하지 않는다.
위 조건으로 eight와 line 그래프의 수를 구하면 donut도 자동으로 구해진다.
⚠️ 추가적인 변수
이 문제의 변수는 inbound와 outbound가 모두 포함되지 않는 노드가 존재한다는 것이다. 이런 노드는 어떤 그래프에 속하지 않으므로 계산을 제외해줘야 한다.
👉 line 그래프의 판별 조건이 변경
- edge가 아니다
- inbound가 반드시 하나 이상일 것 (edge노드에 의해 2가 될 수 있음)
- outbound는 0일 것
이렇게 두면 정확하게 line node를 계산할 수 있다.
정답 코드
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
번외) 사고 오류 코드
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자 모양 그래프 수 ]
- 하나 하나 순회하면서 트리 모양을 분석하려고 했다.
- 시간 복잡도를 생각했으면 이렇게 풀면 안된다는 것을 바로 파악가능

