코딩테스트/개념 및 노하우

최장 증가 부분 수열(LIS) 알고리즘

스키(ski) 2023. 12. 18. 15:52

백준의 11053과 같은 유형을 흔히 `최장 증가 부분 수열(Longest Increasing Subsequence,LIS)라 하는데 이를 구하는 방법은 크게 두가지가 존재한다.

 

1. DP를 이용한 풀이 (가장 기본적 간단, 시간복잡도:O(n^2))

  • 파이썬
import sys
sys_input=sys.stdin.readline

n=int(sys_input())
arr=list(map(int,sys_input().rstrip().split()))
dp=[1]

for i in range(1,n):
    find=0
    for j in range(i):
        if arr[i]>arr[j]:
            find=max(find,dp[j])

    dp.append(find+1)

print(max(dp))

 

  • 자바
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String args[]) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        int[] arr = new int[n];
        int[] dp = new int[n];

        for (int i = 0; i < n; i++) {
            int x = Integer.parseInt(st.nextToken());
            int find = 0;

            for (int j = 0; j < i; j++) {
                if (arr[j] < x) {
                    find = Math.max(find, dp[j]);
                }
            }
            arr[i] = x;
            dp[i] = find + 1;
        }

        System.out.println(Arrays.stream(dp).max().getAsInt());
    }
}

 

 

위의 두 코드와 같이 DP배열에 현재 인덱스까지 포함한 최장 증가 부분 수열을 찾게된다.

 

그리고 이후 DP배열의 최대값을 찾으면 그것이 전체 수열의 LIS가 된다.

 

 

문제점

 

하지만 이 방식은 시간복잡도가 O(n^2)이기에 수열의 길이가 상당히 긴 경우 시간 초과가 발생할수 있다.

 

 

 2. 이분탐색을 이용한 풀이 (시간복잡도:O(nlog n))

이분탐색을 적용하여 LIS를 구할수 있다. 이분탐색의 시간복잡도는 O(log n)이고 이를 모든 인덱스에 대해 수행하기에 시간복잡도는 O(nlog n)이 되게 된다.

 

작동 방식은 아래와 같다.

 

0. 초기 arr[0]의 값을 빈 배열(lis)의 맨앞에 넣어준다.

 

1. 첫번째 인덱스부터 마지막까지 아래와 같은 과정을 수행한다.

 

1.a arr[i]가 lis의 마지막 인덱스의 값보다 큰경우

    - lis의 뒤에 arr[i]를 넣어준다.(python의 append)

 

1.b arr[i]가 lis의 마지막 인덱스의 값보다 작거나 같은경우

    - arr[i]가 lis에 들어갈 위치를 이분탐색을 통해 찾는다.(lis를 기준으로 arr[i]의 이분탐색)

    - 이분탐색 결과로 나온 인덱스에 arr[i]를 넣어준다.

 

 

적용 예시

 

  • 파이썬 (bisect를 이용)
import sys
from bisect import bisect_left

sys_input=sys.stdin.readline

n=int(sys_input())
arr=list(map(int,sys_input().rstrip().split()))
ans=[arr[0]]

for i in range(1,n):
    if ans[-1]<arr[i]:
        ans.append(arr[i])
    else:
        idx=bisect_left(ans,arr[i])
        ans[idx]=arr[i]

print(len(ans))

 

  • 자바
import java.util.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class Main {
    public static int n;
    public static int[] arr;
    public static int[] ans;
    public static int len;

    public static int bs(int x) {
        int low = -1;
        int high = len;

        while (low + 1 < high) {
            int mid = (low + high) / 2;

            if (ans[mid] >= x) {
                high = mid;
            } else {
                low = mid;
            }
        }

        return high;
    }

    public static void main(String args[]) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        n = Integer.parseInt(br.readLine());
        st = new StringTokenizer(br.readLine());

        arr = new int[n];
        ans = new int[n];
        len = 1;

        for (int i = 0; i < n; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        ans[0] = arr[0];

        for (int i = 1; i < n; i++) {
            if (ans[len - 1] < arr[i]) {
                ans[len] = arr[i];
                len += 1;
            } else {
                int idx = bs(arr[i]);
                ans[idx] = arr[i];
            }
        }

        System.out.println(len);
    }
}

 

 

결론

 

문제의 시간 제한과 수열의 최대 길이 조건을 보고 적절하게 둘을 사용하자.