두 문자열의 최장 공통 부분 수열(LCS) 의 길이와, 그 중 하나의 LCS 문자열을 출력해야 한다.
단, 메모리 제한이 3MB라서 전형적인 \(O(nm)\) DP 테이블(복원용 trace 포함)을 저장하면 메모리 초과가 난다.
문제 정보
문제 링크: https://www.acmicpc.net/problem/7727
문제 요약:
- 길이 \(n_1, n_2\)인 두 소문자 문자열이 주어진다.
- LCS 길이 \(K\)와, 길이 \(K\)인 LCS 하나를 출력한다.
제한 조건:
- 시간 제한: 2초
- 메모리 제한: 3MB
- \(1 \le n_1, n_2 \le 10{,}000\)
입출력 예제
입력 1:
| |
출력 1:
| |
접근 방식
핵심 관찰 1: 길이만 구하면 2행 DP로 충분하지만, “수열 복원”이 문제
LCS 길이는 다음 점화식으로 구한다.
\[ dp[i][j] = \begin{cases} dp[i-1][j-1] + 1 & (a_i = b_j) \\ \max(dp[i-1][j], dp[i][j-1]) & (a_i \ne b_j) \end{cases} \]길이만 필요하다면 이전 행만 유지하는 2행(rolling) DP로 공간을 \(O(m)\)까지 줄일 수 있다.
하지만 LCS 문자열까지 출력하려면 보통 전체 DP 테이블/trace가 필요해 메모리 제한 3MB에서 불가능하다.
핵심 관찰 2: Hirschberg 알고리즘으로 “선형 메모리 LCS 복원”이 가능
Hirschberg는 LCS를 다음처럼 분할정복으로 복원한다.
- 문자열 \(A\)를 중간에서 \(A_L, A_R\)로 자른다.
- \(A_L\)과 \(B\)에 대한 LCS 길이 배열 \(L_1[k] = LCS(A_L, B[0..k))\)를 rolling DP로 구한다.
- \(A_R\)과 \(B\)에 대해 역방향으로 \(L_2[k] = LCS(A_R, B[k..m))\)에 해당하는 값을 역시 rolling DP로 구한다.
- \(L_1[k] + L_2[k]\)가 최대가 되는 분할점 \(k\)를 택하면,
- LCS는 \(LCS(A_L, B[0..k))\)와 \(LCS(A_R, B[k..m))\)로 나뉘어 복원 가능하다.
이렇게 하면 복원 과정에서도 DP 테이블 전체를 저장하지 않고,
항상 길이 \(m+1\)짜리 배열 2개 정도만 사용하므로 메모리는 \(O(m)\)이다.
또한 이 문제는 LCS 길이가 최대 10,000이므로 DP 배열 원소를 uint16_t로 저장해 메모리를 더 줄일 수 있다.
알고리즘 설계 (Mermaid Flowchart)
flowchart TD
A["입력: n1, n2, 문자열 A, B"] --> S{"|B| > |A| ?"}
S -->|"yes"| SW["A,B 스왑DP 배열을 더 짧은 문자열 기준으로"]
S -->|"no"| K["그대로 진행"]
SW --> H["Hirschberg(A,B) 호출"]
K --> H
H --> D{"A 또는 B가 비었나?"}
D -->|"yes"| Z["빈 문자열 반환"]
D -->|"no"| E{"|A| == 1 또는 |B| == 1?"}
E -->|"yes"| B1["선형 탐색으로 포함 여부 확인가능하면 해당 문자 1개 반환"]
E -->|"no"| F["A를 중간에서 분할: A_L, A_R"]
F --> G["Forward DP: L1[k] = LCS(A_L, B[0..k))"]
G --> R["Reverse DP: L2[k] = LCS(A_R, B[k..m))"]
R --> M["k* = argmax_k (L1[k] + L2[k])"]
M --> L["왼쪽: Hirschberg(A_L, B[0..k*))"]
M --> RR["오른쪽: Hirschberg(A_R, B[k*..m))"]
L --> OUT["결과 이어붙이기"]
RR --> OUT
OUT --> P["K=|LCS| 출력LCS 문자열 출력"]
복잡도 분석
| 항목 | 복잡도 | 비고 |
|---|---|---|
| 시간 복잡도 | \(O(nm)\) | 분할정복이지만 총 DP 계산량은 같은 차수 |
| 공간 복잡도 | \(O(\min(n,m))\) | DP 1차원 배열 2개 + 재귀 스택 |
코너 케이스 및 실수 포인트
| 케이스 | 설명 | 처리 방법 |
|---|---|---|
| 한 문자열이 길이 1 | 분할정복보다 직접 확인이 빠르고 단순 | 해당 문자가 다른 문자열에 있으면 1글자 LCS |
| 정답이 여러 개 | 어떤 LCS든 허용 | 임의의 분할점 선택(최대값 중 아무거나) |
| 메모리 제한 3MB | int DP도 가능하지만 더 안전하게 | DP 값을 uint16_t로 저장 |
| 성능 | \(10^8\)급 연산 | ios::sync_with_stdio(false) + 단순 루프 |
구현 코드
C++
| |
![Featured image of post [Algorithm] C++ 백준 7727번: Byephone](/post/algorithm/2025-12-19-boj-7727-byephone-hirschberg-lcs-cpp-solution/wordcloud_hu_5f2dea0132c0b016.png)
![[Algorithm] C++ 백준 5813번: 이상적인 도시](/post/algorithm/2025-12-19-boj-5813-ideal-city-cpp-solution/wordcloud_hu_8653825e20480456.png)
![[Algorithm] C++ 백준 6223번: Cow Sorting](/post/algorithm/2025-12-19-boj-6223-cow-sorting-cpp-solution/wordcloud_hu_f6ecf74bd68c4451.png)
![[Algorithm] C++ 백준 7727번: Byephone](/post/algorithm/2025-12-19-boj-7727-byephone-hirschberg-lcs-cpp-solution/wordcloud_hu_7264c733e5d43dbc.png)
![[Algorithm] C++ 백준 8155번: Postering](/post/algorithm/2025-12-19-boj-8155-postering-monotonic-stack-cpp-solution/wordcloud_hu_99a39d8565dae067.png)
![[Algorithm] C++ 백준 14449번: Balanced Photo](/post/algorithm/2025-12-15-boj-14449-balanced-photo-cpp-solution/wordcloud_hu_c3927a1977c9e917.png)
![[Algorithm] C++ 백준 18438 번 : LCS 5](/post/algorithm/2025-08-12-boj-18438-lcs-5-cpp-solution/wordcloud_hu_5e6806967f9617ac.png)
![[Algorithm] C++ 백준 2586번: 소방차](/post/algorithm/2025-12-19-boj-2586-firetruck-cpp-solution/wordcloud_hu_c7df555f7b0ecd9e.png)
![[Algorithm] C++ 백준 32190번: Ian Sequences](/post/algorithm/2025-12-19-boj-32190-ian-sequences-cpp-solution/wordcloud_hu_266416599cd1461b.png)
![[Algorithm] C++ 백준 3948번: 홍준이의 친위대](/post/algorithm/2025-12-19-boj-3948-hongjuns-royal-guards-cpp-solution/wordcloud_hu_72c9c02c1d626af1.png)
![[Algorithm] C++ 백준 5498번: Batch Scheduling](/post/algorithm/2025-12-19-boj-5498-batch-scheduling-cpp-solution/wordcloud_hu_ea128457d0ff4d1c.png)