이 문제에서는 두 개의 문자열 str1과 str2가 주어집니다. 우리의 목표는 사전순(lexicographical order)으로 가장 긴 공통 부분 수열(Longest Common Subsequence)을 모두 출력하는 프로그램을 작성하는 것입니다.
문제 예시
입력: str1 = "gfare", str2 = "rfare"
출력: fare
위 예시에서 두 문자열의 최장 공통 부분 수열은 길이가 4인 "fare" 하나입니다. 하지만 최장 공통 부분 수열은 여러 개 존재할 수 있으며, 그런 경우에는 중복 없이 모든 수열을 사전순으로 출력해야 합니다.
해결 접근 방법
이 문제는 크게 두 단계로 나누어 해결할 수 있습니다.
- LCS 길이 계산: 동적 계획법(Dynamic Programming)을 활용하여 재귀 함수와 메모이제이션(memoization)으로 두 문자열의 최장 공통 부분 수열 길이를 구하고, 그 결과를 2차원 DP 배열에 저장합니다. 한 번 계산된 값은 재활용되므로 지수 시간이 걸리는 완전 탐색보다 훨씬 효율적입니다.
- 사전순 출력: 백트래킹(backtracking) 기법으로 'a'부터 'z'까지 문자를 차례대로 검사합니다. 특정 위치 (i, j)에서 문자 ch를 선택했을 때, 그 위치에서 시작하는 LCS 길이가 앞으로 채워야 할 길이와 일치한다면 해당 문자를 결과 버퍼에 추가하고 재귀 호출을 진행합니다. 'a'부터 'z' 순서로 탐색하기 때문에 별도의 정렬 과정 없이 자연스럽게 사전순에 가까운 결과를 얻을 수 있습니다.
코드에서 calcLCSLenght 함수는 (i, j) 위치에서 시작하는 LCS의 길이를 반환하고, printAllLCS 함수는 이 정보를 활용해 유효한 경로만 따라가며 모든 최장 공통 부분 수열을 생성합니다.
C++ 구현 코드
#include<iostream>
#include<cstring>
#define MAX 100
using namespace std;
int LCSLength = 0;
int DP[MAX][MAX];
int calcLCSLenght(string str1, string str2, int l1, int l2, int i, int j) {
int &lcsLen = DP[i][j];
if (i==l1 || j==l2)
return lcsLen = 0;
if (lcsLen != -1)
return lcsLen;
lcsLen = 0;
if (str1[i] == str2[j])
lcsLen = 1 + calcLCSLenght(str1, str2, l1, l2, i+1, j+1);
else
lcsLen = max(calcLCSLenght(str1, str2, l1, l2, i+1, j), calcLCSLenght(str1, str2, l1, l2, i, j+1));
return lcsLen;
}
void printAllLCS(string str1, string str2, int l1, int l2, char data[], int index1, int index2, int currentLCSlength) {
if (currentLCSlength == LCSLength) {
data[currentLCSlength] = '\0';
puts(data);
return;
}
if (index1==l1 || index2==l2)
return;
for (char ch='a'; ch<='z'; ch++) {
bool done = false;
for (int i=index1; i<l1; i++) {
if (ch==str1[i]) {
for (int j=index2; j<l2; j++) {
if (ch==str2[j] && calcLCSLenght(str1, str2, l1, l2, i, j) == LCSLength-currentLCSlength) {
data[currentLCSlength] = ch;
printAllLCS(str1, str2, l1, l2, data, i+1, j+1, currentLCSlength+1);
done = true;
break;
}
}
}
if (done)
break;
}
}
}
int main() {
string str1 = "xysxysx", str2 = "xsyxsyx";
int l1 = str1.length(), l2 = str2.length();
memset(DP, -1, sizeof(DP));
LCSLength = calcLCSLenght(str1, str2, l1, l2, 0, 0);
char data[MAX];
cout<<"All longest common sub-sequences in lexicographical order are\n";
printAllLCS(str1, str2, l1, l2, data, 0, 0, 0);
return 0;
}
실행 결과
All longest common sub-sequences in lexicographical order are xsxsx xsxyx xsysx xysyx xyxsx xyxyx
위 실행 결과를 보면, 입력 문자열 str1 = "xysxysx", str2 = "xsyxsyx"에 대해 길이가 5인 최장 공통 부분 수열이 총 6개 존재하며, 프로그램은 이들을 하나씩 출력합니다. 이처럼 DP 테이블과 백트래킹을 조합하면 최장 공통 부분 수열의 길이 계산과 실제 수열의 열거를 모두 효율적으로 처리할 수 있습니다.