문제
수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.
예를 들어, 수열 인 경우에 가장 긴 증가하는 부분 수열의 길이는 4이다.
입력
첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다.
둘째 줄에는 수열 를 이루고 있는 가 주어진다.
출력
첫째 줄에 수열 A의 가장 긴 증가하는 부분 수열의 길이를 출력한다.
풀이
현재 코드의 핵심은 lis[len]을 '길이가 len + 1인 증가 부분수열이 가질 수 있는 가장 작은 끝값'으로 관리하는 것이다. 수열을 왼쪽부터 보면서 새 값이 lis의 마지막보다 크면 뒤에 붙여 길이를 늘리고, 아니라면 이분 탐색으로 들어갈 자리를 찾아 그 값을 교체한다.
배열 값을 덮어쓴다고 해서 실제 LIS 길이가 줄어드는 것은 아니다. 오히려 끝값을 더 작게 유지할수록 뒤에 더 많은 수가 붙을 수 있으므로, 최종적으로 lis의 길이가 곧 LIS 길이가 된다.
코드
import java.io.*;
import java.util.*;
public class Main {
static int[] arr, LIS;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
arr = new int[N];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++)
arr[i] = Integer.parseInt(st.nextToken());
System.out.println(solve(N));
}
static int solve(int N) {
LIS = new int[N];
LIS[0] = arr[0];
int cnt = 1;
for (int i = 1; i < N; i++) {
if (arr[i] > LIS[cnt - 1]) {
LIS[cnt++] = arr[i];
} else {
int low = 0;
int high = cnt - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (LIS[mid] < arr[i]) low = mid + 1;
else high = mid - 1;
}
LIS[low] = arr[i];
}
}
return cnt;
}
}복잡도
- 시간 복잡도: 각 원소마다 이분 탐색으로 위치를 찾으므로 이다.
- 공간 복잡도: LIS 후보 배열을 저장하므로 이다.
마무리
현재 코드의 핵심은 lis[len]을 '길이가 len + 1인 증가 부분수열이 가질 수 있는 가장 작은 끝값'으로 관리하는 것이다.
