최장 증가 부분 수열 (LIS)
최장 증가 부분 수열 (LIS, Longest Increasing Subsequence)
어떤 수열의 부분 수열 중 수열의 순서를 유지하면서 크기가 점점 증가하는 가장 긴 부분 수열을 말한다.
예를 들어 크기가 6인 수열 { 5, 8, 9, 4, 2, 3 }이 있다면, 이 수열의 LIS는 { 5, 8, 9 }이며 그 크기는 3이다.
LIS는 여러 개가 있을 수도 있다.
LIS의 크기를 구하는 방법에는 두 가지가 있는데 하나는 동적 계획법을 이용하는 방법이고, 다른 하나는 이분 탐색을 이용하는 방법이다.
동적 계획법(DP, Dynamic Programming)
DP 배열에 현재 인덱스에서의 최장 증가 부분 수열의 길이를 저장하는 방법이다.
이전 인덱스들 중 현재 인덱스의 값보다 작으면서 LIS 크기가 가장 큰 값을 찾아야 하기 때문에 모든 이전 인덱스를 탐색해야하므로 시간 복잡도는 O(N^2)이다.
코드
import java.util.*;
public class LIS_DP {
public static void main (String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[] arr = new int[N];
int[] lis = new int[N];
for (int i = 0; i < N; i++) {
arr[i] = sc.nextInt();
}
int max = 0;
for (int i = 0; i < N; i++) {
lis[i] = 1;
for (int j = 0; j < i; j++) {
if (arr[j] < arr[i] && lis[i] < lis[j] + 1) {
lis[i] = lis[j] + 1;
}
}
max = Math.max(max, list[i]);
}
System.out.println(Arrays.toString(lis));
System.out.println(max);
}
}
이진 탐색(Binary Search)
C[k] : 길이 k의 증가 수열에 대해 k 자리에 올 수 있는 가능한 가장 작은 값을 C[k]에 저장한다.
각 원소마다 C[]를 갱신하기 위해 이진 탐색을 수행한다.
이진 탐색이기 때문에 시간 복잡도는 O(nlogn)이다.
dp를 사용한 방법에서는 dp의 값이 LIS의 길이였지만, 이진 탐색에서는 k가 LIS의 길이가 된다.
즉, C[]가 채워지는 길이가 LIS의 길이가 된다.
다음 코드는 자바의 이진탐색 함수를 사용해 size를 늘려나가며 C 배열에서 현재 인덱스의 원소가 들어갈 위치를 찾아 넣는 식으로 구현하였다.
이때 size가 LIS의 길이가 된다.
코드
import java.util.*;
public class LIS_BS {
public static void main (String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[] arr = new int[N];
int[] C = new int[N];
for (int i = 0; i < N; i++) {
arr[i] = sc.nextInt();
}
int size = 0;
for (int i = 0; i < N; i++) {
int pos = Arrays.binarySearch(C, 0, size, arr[i]);
if (pos >= 0) {
continue;
}
int insertPos = Math.abs(pos) - 1;
C[insertPos] = arr[i];
if (insertPos == size) {
size++;
}
}
System.out.println(size);
}
}