LCS(Longest Common Subsequence, 최장 공통 부분 수열)문제는 두 수열이 주어졌을 때, 모두의 부분 수열이 되는 수열 중 가장 긴 것을 찾는 문제이다.
예를 들어, ACAYKP와 CAPCAK의 LCS는 ACAK가 된다.
첫째 줄과 둘째 줄에 두 문자열이 주어진다. 문자열은 알파벳 대문자로만 이루어져 있으며, 최대 1000글자로 이루어져 있다.
첫째 줄에 입력으로 주어진 두 문자열의 LCS의 길이를 출력한다.
이 문제는 LCS(최장 공통 부분 수열)인데, 부분 수열로 나올 수 있는 수열 중 가장 긴 것을 찾는 문제이다. 먼저 풀기에 앞서 이해를 돕기 위해 해당 영상을 먼저 보고 오는 것을 추천한다. (영상링크)
주어진 예제 str1 = ACAYKP , str2 = CAPCAK를 가지고 설명을 하겠다.
문자열 str1에서 나올 수 있는 부분 수열은 { }, {A}, {C} ...{A, C} ... {A, C, A, Y, K, P}이 있고, 마찬가지로 str2에서는 { }, {C}, {A}, ... {C, A} ... {C, A, P, C, A, K} 가 있다.
여기서 공통적인 부분 문자열 중에서 가장 긴 길이를 가진 문자열을 찾으면 되는 것이다.
예를들어, = {, , ... } , = {, , ... } 가 있다고 치자. 그때 LCS(, ) = , 의 최장 길이 공통 부분 수열의 길이이다.
이제 LCS(i, j)일 때를 보면 = , , ... , 와 = , , ... , 가 나온다. 여기서 와 가 같을 때와 다를 때를 확인하면 된다.
일 때 즉, 마지막 문자가 같은 경우 그 이전까지 구한 LCS(i-1, j-1) 에 +1 를 해주면 된다.
하지만 != 일 때 (다른 경우) X의 마지막 문자 혹은 Y의 마지막 문자 중 하나가 뽑힐 때를 생각해볼 수 있다. (둘 다 동시에 뽑는 경우는 없다.) 즉, X의 마지막 문자가 뽑힌 경우에는 Y의 마지막 문자가 전까지의 LCS 와 Y의 마지막 문자가 뽑힌 경우 X의 마지막 문자 전까지의 LCS 2가지가 나올 수 있다.
다시 정리하면 LCS(i, j-1) 과 LCS(i-1, j) 이 2가지에서 제일 긴 것을 찾으면 된다.
점화식을 세우면, 아래와 같다.
LCS(i, j) =
- LCS(i-1, j-1) + 1 if
- max(LCS(i, j-1) , LCS(i-1, j)) if !=
이제 주어진 예제를 가지고 표를 그려 확인해보자. 최종적으로는 제일 오른쪽 밑에 있는 값이 답이다.
str1 / str2 | 0 | C | A | P | C | A | K |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | |||||||
C | |||||||
A | |||||||
Y | |||||||
K | |||||||
P |
여기서 0은 빈 문자열을 의미한다. 이제 행단위로 하나씩 채워나가게 되면 아래와 같이 채워질 수 있다.
str1 / str2 | 0 | C | A | P | C | A | K |
---|---|---|---|---|---|---|---|
0 | 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 |
왼쪽위를 (1,1)로 보고 (4,3) 위치를 설명하겠다.
(4,3) 은 str1 의 A와 str2 의 A 를 비교하는 것인데, A == A 이기 때문에 (4-1, 3-1) + 1를 해주어 2가 채워짐을 알 수 있다.
추가적으로 (4,2)위치를 보면 str1의 A와 str2의 C와 비교하니 다르다는 것을 알았고 이때, (4,2-1) = 0 과 (4-3, 2) = 1 중에 최대가 1이기 때문에 1로 채워짐을 알 수 있다.
따라서 LCS = 4 가 나옴을 알 수 있다.
해당 점화식을 코드로 옮겼으니 확인하면 되겠다.
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
char[] str1 = sc.nextLine().toCharArray();
char[] str2 = sc.nextLine().toCharArray();
int[][] dp = new int[str1.length + 1][str2.length + 1];
for (int i = 1; i <= str1.length; i++) {
for (int j = 1; j <= str2.length; j++) {
// 같을 경우
if (str1[i - 1] == str2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
// 같지 않을 경우
else {
dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
}
}
}
System.out.println(dp[str1.length][str2.length]);
}
}