ALGORITHM NOTE1

BOJ 12015 - 가장 긴 증가하는 부분 수열 2

언제까지 증가하는 거에요?

#algorithm#boj#gold#binary-search#lis
아카이브로 돌아가기

문제 링크

문제

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.

예를 들어, 수열 A={10,20,10,30,20,50}A = \{10, 20, 10, 30, 20, 50\}인 경우에 가장 긴 증가하는 부분 수열의 길이는 4이다.

입력

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다.

둘째 줄에는 수열 AA를 이루고 있는 AiA_i가 주어진다. (1Ai1,000,000)(1 \le A_i \le 1{,}000{,}000)

출력

첫째 줄에 수열 A의 가장 긴 증가하는 부분 수열의 길이를 출력한다.

풀이

현재 코드의 핵심은 lis[len]을 '길이가 len + 1인 증가 부분수열이 가질 수 있는 가장 작은 끝값'으로 관리하는 것이다. 수열을 왼쪽부터 보면서 새 값이 lis의 마지막보다 크면 뒤에 붙여 길이를 늘리고, 아니라면 이분 탐색으로 들어갈 자리를 찾아 그 값을 교체한다.

배열 값을 덮어쓴다고 해서 실제 LIS 길이가 줄어드는 것은 아니다. 오히려 끝값을 더 작게 유지할수록 뒤에 더 많은 수가 붙을 수 있으므로, 최종적으로 lis의 길이가 곧 LIS 길이가 된다.

코드

java
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;
    }
}

복잡도

  • 시간 복잡도: 각 원소마다 이분 탐색으로 위치를 찾으므로 O(NlogN)O(N \log N)이다.
  • 공간 복잡도: LIS 후보 배열을 저장하므로 O(N)O(N)이다.

마무리

현재 코드의 핵심은 lis[len]을 '길이가 len + 1인 증가 부분수열이 가질 수 있는 가장 작은 끝값'으로 관리하는 것이다.