4256번 - 트리
문제 풀이 시간 : 27분
문제 요약
- 이진 트리의 전위 순회(preorder) 결과와 중위 순회(inorder) 결과가 주어짐
- 두 순회 결과로 만들어지는 트리는 항상 유일
- 해당 트리의 후위 순회(postorder) 결과를 구하는 문제
- 노드 개수
n ≤ 1000
문제 풀이
이 문제를 처음 봤을 때는
👉 트리를 직접 복구한 뒤, 다시 후위 순회를 하면 되겠다고 생각했다.
그래서 전위 + 중위 결과를 이용해 트리를 구성한 뒤,
후위 순회를 통해 출력하는 방식으로 접근했다.
🌳 초기 접근 – 트리를 직접 복구하기
전위 순회와 중위 순회의 특징은 다음과 같다.
- 전위 순회:
루트 → 왼쪽 → 오른쪽 - 중위 순회:
왼쪽 → 루트 → 오른쪽
즉,
- 전위 배열의 첫 번째 값은 항상 루트
- 중위 배열에서 해당 루트 값을 기준으로
이 구조를 재귀적으로 반복하면 트리를 복구할 수 있다.
초기 코드
import java.io.*;
import java.util.*;
public class Main {
public static int[] pre, in;
public static int n;
public static StringBuilder sb;
public static class Node {
int num;
Node left;
Node right;
public Node(int num) {
this.num = num;
}
}
public static Node find(int si, int ei) {
if (si >= ei) {
return null;
}
Map<Integer, Integer> s = new HashMap<>();
for (int i = si; i < ei; i++) {
s.put(in[i], i);
}
int p = -1;
for (int i = 0; i < n; i++) {
if (s.containsKey(pre[i])) {
p = pre[i];
break;
}
}
Node root = new Node(p);
root.left = find(si, s.get(p));
root.right = find(s.get(p) + 1, ei);
return root;
}
public static void post(Node root) {
if(root.left!=null) {
post(root.left);
}
if(root.right!=null) {
post(root.right);
}
sb.append(root.num).append(" ");
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
int T = Integer.parseInt(br.readLine());
for (int t = 0; t < T; t++) {
n = Integer.parseInt(br.readLine());
pre = new int[n];
in = new int[n];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
pre[i] = Integer.parseInt(st.nextToken());
}
int mid = -1;
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
in[i] = Integer.parseInt(st.nextToken());
if (in[i] == pre[0]) {
mid = i;
}
}
Node root = find(0,n);
sb = new StringBuilder();
post(root);
System.out.println(sb.toString());
}
}
}이 방식은 논리적으로는 맞지만,
재귀 호출마다 Map을 새로 만들고,
전위 배열을 매번 처음부터 탐색하는 구조였다.
👉 즉, 시간 복잡도가 에 가까운 비효율적인 구조였다.
노드 개수가 많아지면 충분히 시간 초과가 발생할 수 있는 코드였다.
🔥 개선 아이디어
곰곰이 생각해보니,
굳이 트리를 객체로 만들 필요가 없었다.
우리가 필요한 건 트리 구조가 아니라
👉 후위 순회 결과 뿐이다.
그렇다면
- 전위에서 루트를 하나 꺼내고
- 중위에서 루트 위치를 찾아 구간을 나누고
- 왼쪽 → 오른쪽 재귀 호출 후
- 마지막에 루트를 출력하면 된다
이 방식이면 트리를 만들지 않고 바로 후위 순회 결과를 만들 수 있다.
✨ 핵심 아이디어
1️⃣ 전위 배열은 루트를 꺼내는 용도
전위 순회는 항상 루트부터 시작한다.
따라서 preIndex라는 변수를 두고
현재 서브트리의 루트를 하나씩 꺼내면 된다.
int rootVal = pre[preIndex++];2️⃣ 중위 배열은 범위를 나누는 기준
중위 순회는
왼쪽 서브트리 | 루트 | 오른쪽 서브트리구조이기 때문에,
루트의 위치를 기준으로 구간을 나눌 수 있다.
이때 루트 위치를 빠르게 찾기 위해
값 → 인덱스를 저장한 HashMap을 사용했다.
3️⃣ 후위 순서로 출력
후위 순회는
왼쪽 → 오른쪽 → 루트이므로,
find(왼쪽);
find(오른쪽);
출력;순서로 작성하면 된다.
⚙️ 시간 복잡도
- 각 노드를 정확히 한 번씩만 방문
HashMap조회는
따라서 전체 시간 복잡도는 이라서 초기 코드보다 훨씬 효율적인 구조이다.
최종 코드
import java.io.*;
import java.util.*;
public class Main {
public static int[] pre;
public static int n, preIndex;
public static StringBuilder sb;
public static Map<Integer, Integer> in;
public static void find(int si, int ei) {
if (si > ei)
return;
int rootVal = pre[preIndex++];
int rootIdx = in.get(rootVal);
find(si, rootIdx - 1);
find(rootIdx + 1, ei);
sb.append(rootVal).append(" ");
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
int T = Integer.parseInt(br.readLine());
for (int t = 0; t < T; t++) {
in = new HashMap<>();
preIndex = 0;
n = Integer.parseInt(br.readLine());
pre = new int[n];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
pre[i] = Integer.parseInt(st.nextToken());
}
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
in.put(Integer.parseInt(st.nextToken()), i);
}
sb = new StringBuilder();
find(0, n - 1);
System.out.println(sb.toString());
}
}
}함께 읽으면 좋은 글
5430번 - AC
함수 R은 배열을 뒤집는 연산 함수 D는 첫 번째 원소를 버리는 연산 단, 배열이 비어 있을 때 D를 수행하면 error 주어진 함수 문자열을 순서대로 수행한 뒤 최종 배열을 출력하는 문제 처음에는 문제 설명 그대로 R이 나올 때마다 배열을 실제로 뒤집고, D가 나오면 맨 앞…
1966번 - 프린터 큐
여러 문서가 큐에 들어 있고, 각 문서마다 중요도가 있음 맨 앞 문서를 확인했을 때, 더 높은 중요도의 문서가 뒤에 하나라도 있으면 맨 뒤로 보냄 그렇지 않으면 바로 인쇄 특정 위치의 문서가 몇 번째로 인쇄되는지 구하는 문제 이 문제는 프린터의 동작을 그대로 구현하면 되는…
1158 - 요세푸스 문제
1번부터 N번까지 사람이 원형으로 앉아 있음 순서대로 K번째 사람을 제거 모든 사람이 제거될 때까지 반복 제거되는 순서를 출력하는 문제 이 문제는 원형으로 순회하면서 K번째를 제거하는 과정을 그대로 구현하면 된다.
2164번 - 카드2
1부터 N까지 카드가 순서대로 쌓여 있음 맨 위 카드를 버리고, 그다음 맨 위 카드를 맨 아래로 옮기는 과정을 반복 마지막에 남는 카드 번호를 구하는 문제 이 문제는 규칙을 찾기보다, 문제에서 하라는 과정을 그대로 구현하면 된다. 카드 더미에서 반복되는 동작은 두 가지다.