https://www.acmicpc.net/problem/9252
LCS(Longest Common Subsequence, 최장 공통 부분 수열)문제는 두 수열이 주어졌을 때, 모두의 부분 수열이 되는 수열 중 가장 긴 것을 찾는 문제이다.
예를 들어, ACAYKP와 CAPCAK의 LCS는 ACAK가 된다.
ACAYKP
CAPCAK
첫째 줄과 둘째 줄에 두 문자열이 주어진다. 문자열은 알파벳 대문자로만 이루어져 있으며, 최대 1000글자로 이루어져 있다.
4
ACAK
첫째 줄에 입력으로 주어진 두 문자열의 LCS의 길이를, 둘째 줄에 LCS를 출력한다.
LCS가 여러 가지인 경우에는 아무거나 출력하고, LCS의 길이가 0인 경우에는 둘째 줄을 출력하지 않는다.
✅ 아이디어
- 일단, DP를 활용하여 LCS의 길이를 구한다.
- dp[i][j] → A의 앞 i글자와 B의 앞 j글자까지 고려했을 때의 LCS의 길이
- 점화식은 다음과 같다. (bottom-up)
- A[i] == B[j] 인 경우 → dp[i][j] = dp[i-1][j-1] + 1
- A[i] != B[j] 인 경우 → dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- 그리고, dp 테이블 역추적을 통해 실제 LCS 문자열을 구한다.
✅ 시간 복잡도
- dp[i][j]를 채우는 과정: O(N × M)
- dp 테이블을 역추적하는 과정: O(N + M)
- 전체 시간 복잡도: O(N × M) ≈ O(1,000,000) → 최대 1000 × 1000 → 충분히 빠름!
✅ 자바 코드
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
char[] A = br.readLine().toCharArray();
char[] B = br.readLine().toCharArray();
int lenA = A.length;
int lenB = B.length;
int[][] dp = new int[lenA + 1][lenB + 1];
// LCS 길이 구하기 (DP)
for (int i = 1; i <= lenA; i++) {
for (int j = 1; j <= lenB; j++) {
if (A[i - 1] == B[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// LCS 문자열 찾기 (역추적)
StringBuilder sb = new StringBuilder();
int i = lenA, j = lenB;
while (i > 0 && j > 0) {
if (A[i - 1] == B[j - 1]) { // 공통 문자면 LCS에 추가, 대각선으로 이동
sb.append(A[i - 1]);
i--;
j--;
} else if (dp[i - 1][j] >= dp[i][j - 1]) { // 더 큰 값으로 이동
i--;
} else {
j--;
}
}
System.out.println(dp[lenA][lenB]);
if (sb.length() > 0) System.out.println(sb.reverse()); // 거꾸로 추가했으므로 뒤집어서 출력
}
}
1. LCS 길이 구하기 (dp[i][j])
- 두 문자열을 비교하면서 DP 테이블을 채움
- 두 문자가 같으면 대각선 위 값 +1
- 다르면 위쪽 or 왼쪽 중 큰 값 선택
2. LCS 문자열 구하기 (역추적)
- dp[lenA][lenB]에서 시작해서 거꾸로 탐색
- 같은 문자를 만나면, LCS 문자열에 추가 & 대각선 위로 이동
- 다르면, 더 큰 값이 있는 방향으로 이동
- LCS 문자열이 거꾸로 만들어지므로, 마지막에 뒤집어서 출력
📌 예시
1. 입력
ACAYKP
CAPCAK
2. DP 테이블
s1 \ s2 | - | C | A | P | C | A | K |
- | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
C | 0 | 1 | 1 | 1 | 2 | 2 | 2 |
A | 0 | 1 | 2 | 2 | 2 | 3 | 3 |
Y | 0 | 1 | 2 | 2 | 2 | 3 | 3 |
K | 0 | 1 | 2 | 2 | 2 | 3 | 4 |
P | 0 | 1 | 2 | 3 | 3 | 3 | 4 |
3. 출력
4
ACAK
📌 top-down 방식의 경우 (재귀)
// 초기화 (탑다운 방식)
for (int i = 0; i <= lenA; i++) {
for (int j = 0; j <= lenB; j++) {
dp[i][j] = -1; // 아직 계산되지 않음을 표시
}
}
System.out.println(LCS(lenA, lenB));
...
// 재귀 함수 (탑다운 방식)
static int LCS(int i, int j) {
if (i == 0 || j == 0) return 0; // 문자열이 없는 경우
if (dp[i][j] != -1) return dp[i][j]; // 이미 계산된 경우
if (A[i - 1] == B[j - 1]) { // 같은 문자면 대각선 이동
return dp[i][j] = LCS(i - 1, j - 1) + 1;
} else { // 다르면 위쪽 또는 왼쪽 중 큰 값 선택
return dp[i][j] = Math.max(LCS(i - 1, j), LCS(i, j - 1));
}
}
- LCS(i, j)는 A[0...i]와 B[0...j]의 LCS 길이를 반환하는 함수
- 메모이제이션 (dp[i][j] 값이 -1이 아닐 경우)으로 중복 계산 방지
- 기저 사례: i == 0 또는 j == 0일 때 LCS 길이는 0
'오늘의 백준 문제' 카테고리의 다른 글
[오늘의 백준 문제] 2110번 공유기 설치 - 이진 탐색, 파라메트릭 서치 (1) | 2025.03.24 |
---|---|
[오늘의 백준 문제] 10026번 적록색약 - DFS (1) | 2025.03.23 |
[오늘의 백준 문제] 9251번 LCS - DP (1) | 2025.03.21 |
[오늘의 백준 문제] 5430번 AC - 구현, Deque(덱) (1) | 2025.03.20 |
[오늘의 백준 문제] 9012번 괄호 - 문자열 (0) | 2025.03.20 |