문제
이진트리를 다음의 규칙에 따라 행과 열에 번호가 붙어있는 격자 모양의 틀 속에 그리려고 한다. 이때 다음의 규칙에 따라 그리려고 한다.
- 이진트리에서 같은 레벨(level)에 있는 노드는 같은 행에 위치한다.
- 한 열에는 한 노드만 존재한다.
- 임의의 노드의 왼쪽 부트리(left subtree)에 있는 노드들은 해당 노드보다 왼쪽의 열에 위치하고, 오른쪽 부트리(right subtree)에 있는 노드들은 해당 노드보다 오른쪽의 열에 위치한다.
- 노드가 배치된 가장 왼쪽 열과 오른쪽 열 사이엔 아무 노드도 없이 비어있는 열은 없다.
이와 같은 규칙에 따라 이진트리를 그릴 때 각 레벨의 너비는 그 레벨에 할당된 노드 중 가장 오른쪽에 위치한 노드의 열 번호에서 가장 왼쪽에 위치한 노드의 열 번호를 뺀 값 더하기 1로 정의한다. 트리의 레벨은 가장 위쪽에 있는 루트 노드가 1이고 아래로 1씩 증가한다.
아래 그림은 어떤 이진트리를 위의 규칙에 따라 그려 본 것이다. 첫 번째 레벨의 너비는 1, 두 번째 레벨의 너비는 13, 3번째, 4번째 레벨의 너비는 각각 18이고, 5번째 레벨의 너비는 13이며, 그리고 6번째 레벨의 너비는 12이다.

우리는 주어진 이진트리를 위의 규칙에 따라 그릴 때에 너비가 가장 넓은 레벨과 그 레벨의 너비를 계산하려고 한다. 위의 그림의 예에서 너비가 가장 넓은 레벨은 3번째와 4번째로 그 너비는 18이다. 너비가 가장 넓은 레벨이 두 개 이상 있을 때는 번호가 작은 레벨을 답으로 한다. 그러므로 이 예에 대한 답은 레벨은 3이고, 너비는 18이다.
임의의 이진트리가 입력으로 주어질 때 너비가 가장 넓은 레벨과 그 레벨의 너비를 출력하는 프로그램을 작성하시오
입력
첫째 줄에 노드의 개수를 나타내는 정수 N(1 ≤ N ≤ 10,000)이 주어진다. 다음 N개의 줄에는 각 줄마다 노드 번호와 해당 노드의 왼쪽 자식 노드와 오른쪽 자식 노드의 번호가 순서대로 주어진다. 노드들의 번호는 1부터 N까지이며, 자식이 없는 경우에는 자식 노드의 번호에 -1이 주어진다.
출력
첫째 줄에 너비가 가장 넓은 레벨과 그 레벨의 너비를 순서대로 출력한다. 너비가 가장 넓은 레벨이 두 개 이상 있을 때에는 번호가 작은 레벨을 출력한다.
예제 입력 1
19
1 2 3
2 4 5
3 6 7
4 8 -1
5 9 10
6 11 12
7 13 -1
8 -1 -1
9 14 15
10 -1 -1
11 16 -1
12 -1 -1
13 17 -1
14 -1 -1
15 18 -1
16 -1 -1
17 -1 19
18 -1 -1
19 -1 -1
예제 출력 1
3 18
힌트
실제 기출문제의 문제 제목은 "이진트리의 너비" 이다.
접근 방법 🧐
입력값을 바탕으로 트리를 만들고 어떤 아이디어로 접근할지를 생각하는 것이 중요하다.
우선 트리의 각 노드를 다음과 같은 클래스로 만들었다.
static class Node {
int parent; // 부모 번호
int number; // 자신의 번호
int leftChild; // 왼쪽 노드 번호
int rightChild; // 오른쪽 노드 번호
public Node(int number, int leftChild, int rightChild) {
this.parent = -1;
this.number = number;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
}
Node를 만들 때 우선 모든 parent를 -1로 설정해야 한다. 이 문제에서 루트 노드는 무조건 1부터 시작한다는 표현이 없기 때문이다. 따라서 루트 노드의 번호가 2, 3 또는 100일 수도 있다.
그래서 각 Node마다 parent를 -1로 초기화해놓고 입력값을 바탕으로 각 Node에 값을 할당할 때, parent값을 바꿔줄 것이다. 그러면 나중에 루트 Node를 찾을 때 편하다.
모든 노드 중에 parent가 변하지 않고 여전히 -1인 것을 찾으면, 그게 바로 루트 노드가 되는 것이다.
그리고 필요한 것은 각 레벨(깊이)그룹마다 제일 왼쪽에 있는 값의 x좌표 값과, 제일 오른쪽에 있는 값의 x좌표 값이다.

예를 들면 level 2의 가장 왼쪽 좌표는 3이고, 가장 오른쪽 좌표는 15이므로 너비(width)는 15 - 3 + 1이 된다.
즉, 모든 level마다 너비(width)를 구하고 그 중 가장 넓은 값을 출력해야 한다.
그래서 각 level마다 좌표의 최소, 최댓값을 저장해야 한다.
그 값들은 아래의 변수에 저장할 것이다.
static int[] levelMin;
static int[] levelMax;
결국 트리를 천천히 순회하면서 각 레벨의 최소 x좌표, 최대 x좌표 값만 찾으면 된다.
트리 순회 방법
트리 순회 방법에는 3가지 방법이 있다.
1. 전위 순회 (루트 -> 왼쪽 -> 오른쪽)
2. 중위 순회 (왼쪽 -> 루트 -> 오른쪽)
3. 후위 순회 (왼쪽 -> 오른쪽 -> 루트)
이 3가지 방법 중에 하나로 트리를 순회해야 하는데 이 문제에서는 중위 순회가 적절한 방법이 될 것이다!
왜냐하면 트리를 순회하며 모든 Node들을 방문해야 하는데,
중위 순회는 가장 먼저 방문하는 Node가 가장 왼쪽 노드이기 때문이다.
가장 먼저 방문한 Node의 좌표를 1로 설정한다면 두 번째 방문하게 되는 Node는 좌표가 2일 것이다.
중위 순회를 하면 방문하는 순서 그대로가 각 Node의 x좌표가 된다!
위의 예시를 보면, 중위 순회로 인해 가장 먼저 방문하는 Node는 8이고, 이를 x좌표 1로 설정한다.
그 다음 방문은 4이고 x좌표는 아까의 좌표에서 1 높게 설정하면 된다.
결국 우리는 중위 순회를 하면서 그 Node가 level이 몇이고,
현재 이 Node의 x좌표 값이 동일한 level에서 가장 왼쪽 값인지, 가장 오른쪽 값인지만 판단하면 된다.
그리고 가장 왼쪽 값이거나 가장 오른쪽 값이면 위에서 선언했던 levelMin[] 배열 또는 levelMax[]을 업데이트해주면 된다.
내가 쓴 코드 ✍️
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static class Node {
int parent;
int number;
int leftChild;
int rightChild;
public Node(int number, int leftChild, int rightChild) {
this.parent = -1;
this.number = number;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
}
private static int current = 1; // 현재 탐색중인 노드 위치, 노드를 방문할 때마다 +1 증가
private static int maxLevel; // 트리의 최대 레벨(깊이)
private static int[] levelMin, levelMax;
private static Node[] nodes;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
nodes = new Node[N + 1];
levelMin = new int[N + 1];
levelMax = new int[N + 1];
for(int i = 0; i <= N; i++) {
nodes[i] = new Node(i, -1, -1);
levelMin[i] = N;
levelMax[i] = 0;
}
for (int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
int number = Integer.parseInt(st.nextToken());
int leftChild = Integer.parseInt(st.nextToken());
int rightChild = Integer.parseInt(st.nextToken());
nodes[number].leftChild = leftChild;
nodes[number].rightChild = rightChild;
if (leftChild != -1) nodes[leftChild].parent = number;
if (rightChild != -1) nodes[rightChild].parent = number;
}
int rootNodeNumber = 0;
for (int level = 1; level <= N; level++) {
if (nodes[level].parent == -1) {
rootNodeNumber = level;
break;
}
}
inOrderTraversal(rootNodeNumber, 1);
int answerLevel = 1;
int answerWidth = levelMax[1] - levelMin[1] + 1;
for (int level = 2; level <= maxLevel; level++) {
int width = levelMax[level] - levelMin[level] + 1;
if (answerWidth < width) {
answerLevel = level;
answerWidth = width;
}
}
System.out.println(answerLevel + " " + answerWidth);
}
private static void inOrderTraversal(int rootNodeNumber, int level) {
Node rootNode = nodes[rootNodeNumber];
if (maxLevel < level) maxLevel = level;
if (rootNode.leftChild != -1) {
inOrderTraversal(rootNode.leftChild, level + 1);
}
levelMin[level] = Math.min(levelMin[level], current);
levelMax[level] = current;
current++;
if (rootNode.rightChild != -1) {
inOrderTraversal(rootNode.rightChild, level + 1);
}
}
}
구독과 공감은 블로그 운영에 큰 힘이 됩니다! ♡
긍정적인 댓글 남겨주시면 감사드리며,
보완해야 할 점이 있으면 댓글로 남겨주셔도 좋습니다!
'알고리즘 > 백준' 카테고리의 다른 글
[백준] 11053. 가장 긴 증가하는 부분 수열 (자바 JAVA) (3) | 2025.01.19 |
---|---|
[백준] 1946. 신입사원 (자바 JAVA) (0) | 2025.01.12 |
[백준] 1339. 단어 수학 (자바 JAVA) (3) | 2025.01.09 |
[백준] 2206. 벽 부수고 이동하기 (자바 JAVA) (1) | 2025.01.04 |
[백준] 9655. 돌 게임 (자바 JAVA) (2) | 2025.01.03 |