인프런 커뮤니티 질문&답변

박철현님의 프로필 이미지
박철현

작성한 질문수

Do it! 알고리즘 코딩테스트 with JAVA

LCA 빠르게 구하기 Java 코드 시간초과

작성

·

149

0

P11438 문제 교재 코드 그대로 쳤는데 시간초과가 발생하네요 ㅜ

어딜 고쳐야 할까요 ㅠ

import java.util.*;
import java.io.*;

public class Main {
	static ArrayList<Integer>[] tree;
	static int[] depth;
	static int kmax;
	static int[][] parent;
	static boolean[] visited;
	public static void main(String[] args) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int N = Integer.parseInt(br.readLine()); // 노드의 수
		tree = new ArrayList[N + 1];
		for(int i = 1; i <= N; i++) {
			tree[i] = new ArrayList<Integer>();
		}
		StringTokenizer st;
		// 1. 인접리스트에 그래프 데이터 저장하기
		for(int i = 0; i < N - 1; i++) {
			st = new StringTokenizer(br.readLine());
			int s = Integer.parseInt(st.nextToken());
			int e = Integer.parseInt(st.nextToken());
			tree[s].add(e);
			tree[e].add(s);
		}
		depth = new int[N+1];
		visited = new boolean[N + 1];
		int temp = 1;
		kmax = 0;
		while (temp <= N) {  // 최대 가능 depth 구하기
			temp <<= 1;
			kmax++;
		}
		parent = new int[kmax + 1][N + 1];
		// 2. depth와 바로 윗 부모 bfs로 구하기
		bfs(1);
		// 3. 2^k 부모 구하기
		for(int k = 1; k <= kmax; k++) {
			for(int n = 1; n <= N; n++) {
				parent[k][n] = parent[k - 1][parent[k - 1][n]];
			}
		}
		int M = Integer.parseInt(br.readLine());
		// 4. 질의 수행하기
		for(int i = 0; i < M; i++) {
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			int LCA = excuteLCA(a, b);
			System.out.println(LCA);
		}
	}
	static int excuteLCA(int a, int b) {
		// 더 깊은 depth가 뒤에 오도록 변경
		if (depth[a] > depth[b]) {
			int temp = a;
			a = b;
			b = temp;
		}
		for(int k = kmax; k >= 0; k--) {  // 높이 빠르게 맞추기
			if(Math.pow(2, k) <= depth[b] - depth[a]) {
				if(depth[a] <= depth[parent[k][b]]) {
					b = parent[k][b];
				}
			}
		}
		for(int k = kmax; k >=0; k--) {  // 조상 빠르게 찾기
			// 최대 위로 올라가서 같은 부모를 가리키면 k를 1씩 감소하며 다른 지점을 찾음
			if(parent[k][a] != parent[k][b]) {
				a = parent[k][a];
				b = parent[k][b];
			}
		}
		// 여기 온 것은 k = 0일때 고려
		// case 1. k=0, 둘이 같은 노드를 가리킴 -> 그곳이 최소 공통 조상
		// case 2. k=0, 둘이 다른 노드를 가리킴 -> 바로 위에가 최초 공통 조상 -> 2^0 위에 보기
		int LCA = a;
		if(a != b) {
			LCA = parent[0][LCA];
		}
		return LCA;
	}
	// bfs 구현
	private static void bfs(int node) {
		Queue<Integer> queue = new LinkedList<>();
		queue.add(node);
		visited[node] = true;
		int level = 1;
		int now_size = 1;
		int count = 0;
		while(!queue.isEmpty()) {
			int now_node = queue.poll();
			for(int next : tree[now_node]) {
				if(!visited[next]) {
					visited[next] = true;
					queue.add(next);
					parent[0][next] = now_node;  // 부모 노드 저장하기
					depth[next] = level;  // 노드 depth 저장하기
				}
			}
			count++;
			// 자식 노드 모두 검사했는지 확인
			if(count == now_size) {
				count = 0;
				now_size = queue.size();
				level++;
			}
		}
	}
}

답변 1

0

안녕하세요, 인프런 AI 인턴입니다.

님의 Java 코드 시간 초과 문제에 대해 조언드리고자 합니다. P11438 문제의 경우 깊이가 깊은 트리에 대해 LCA(Lowest Common Ancestor, 최소 공통 조상)를 구하는 문제입니다. 주어진 코드는 LCA를 찾기 위해 BFS와 DP를 사용하여 올바른 접근 방식을 취하고 있습니다. 그러나 실제로 문제에서 시간 초과가 발생하는 주된 원인은 다음과 같은 부분에서 개선이 필요할 수 있습니다.

  1. Math.pow의 사용을 피하기: 코드에서 Math.pow(2, k) <= depth[b] - depth[a]로 조건을 검사할 때, Math.pow 함수 대신 비트 연산을 사용해보세요. 예를 들어, 1 << k를 사용하면 Math.pow(2, k)와 동일한 결과를 얻을 수 있지만 훨씬 빠른 연산이 가능합니다. Math.pow 함수는 내부적으로 더 복잡한 연산을 수행하기 때문에 불필요하게 실행 시간이 길어질 수 있습니다.

  2. 최적화 가능한 부분 확인: 전체 코드를 살펴보면서 각 반복문과 조건문에서 불필요한 연산이 없는지, 더 효율적으로 처리할 수 있는 부분이 있는지 확인해보세요. 예를 들어, BFS 부분에서 큐를 사용하여 노드의 깊이와 부모를 설정하는 로직은 상대적으로 최적화하기 어렵지만, excuteLCA 함수 내부의 높이와 조상을 찾는 로직에서 비트 연산 등을 적절히 활용하여 최적화할 수 있는 여지가 있습니다.

  3. 입력 처리 최적화: 문제의 입력 크기가 큰 경우, BufferedReaderStringTokenizer의 사용이 올바르나, 입력 처리 과정에서도 성능 차이가 발생할 수 있습니다. 예를 들어, 입력이 매우 많은 경우, 입력받은 데이터를 처리하는 방식에 따라 실행 시간에 영헥을 줄 수 있으니 이 부분도 점검해볼 필요가 있습니다.

코드의 특정 부분에 초점을 맞추기 보다는 전반적인 실행 시간을 줄이는 방향으로 접근하는 것이 좋습니다. 코드 최적화에 초점을 맞추어 각 부분을 세심하게 검토하고 조정해보세요.

시간 초과 문제는 종종 작은 최적화들이 모여 큰 성능 개선을 이루기 때문에 여러 방법을 시도해보는 것이 중요합니다.

박철현님의 프로필 이미지
박철현

작성한 질문수

질문하기