본문 바로가기
알고리즘

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

by 쪼꼬에몽 2024. 3. 28.

최장 증가 부분 수열 Longest Increasing Subsequence (LIS)


- 배열의 수열에서 연속적으로 값이 증가하는 요소를 연결할 때, 길이가 가장 긴 수열을 찾음.

 

아래와 같은 배열이 있음.

0 1 2 3 4 5
10 20 10 30 20 50

 

증가 부분 수열을 찾으면 

10 - 20 - 30 - 50

10 - 30 - 50

10 - 20 - 50

10 - 50

20 - 30 - 50

20 - 50

30 - 50

임. 최장 증가 부분 수열은 10 - 20 - 30 - 50로, 길이가 4임.

 

LIS를 구하는 방법 1. DP 2. 이진탐색 3.  Segment Tree

 

Dynamic Programming < O(n^2) >


 

DP를 이용하면, 최장 길이는 현재 나온 값보다 작은 이전에 나온 값 중 최장 길이에 + 1을 하면 됨.

 

배열 arr[i]

0 1 2 3 4 5
10 20 10 30 20 50
1 2 1 3 2 4

 

- arr[0]은 항상 길이가 1이 나옴.

 

- arr[1]은 arr[0]보다 값이 크므로 2가 됨.

 

- arr[2]는 arr[1]보다 값이 작음. arr[2]는 arr[0]보다 값이 작음. 따라서, 1로 시작됨.

 

- arr[3]은 3가지 경우가 있음.

 1. arr[3] > arr[2] : 2가 됨.

 2. arr[3] > arr[1] : 3이 됨.

 3. arr[3] > arr[0] : 2가 됨.

이를 위해 이전 인덱스의 값을 확인해야 해서 시간 복잡도는 O(n^2).

 

- arr[4]는 arr[3]보다 값이 작음. arr[4]는 arr[0], arr[2]보다 값이 크므로 2가 됨.

 

- arr[5]는 이전 요소에서 길이가 가장 긴 arr[3]에 1을 더해 4가 됨.

 

Top-down

static int LIS(int n) {
    // 탐색하지 않던 위치의 경우 1로 초기화
    // arr[2]의 경우
    if (dp[n] == 0) {
        dp[n] = 1;

        // n 이전의 노드들을 탐색
        for (int i = n - 1; i >= 0; i--) {
            // 이전의 노드 중 arr[n]의 값보다 작은 걸 발견했을 경우
            // 이전 노드 중 최대를 찾아 1을 더해줌
            // 1을 더하는 이유는 최대 길이가 증가하기 때문
            if (arr[i] < arr[n]) {
                dp[n] = Math.max(dp[n], LIS(i) + 1);
            }
        }
    }
    return dp[n];
}

 

n = 0일 때, dp[0] = 1;

 

n = 1일 때, i는 0.

arr[0] < arr[1]이므로 dp[1] = Math.max(dp[1], LTS(0) + 1);

dp[1] = 2; 

 

dp[2]는 null으므로 1.

 

이런식으로 LIS를 찾음.

Bottom-up

for(int i = 0; i < n; i++) {
    // 모든 원소를 1로 초기화
    dp[i] = 1;

    // 0 ~ i 이전 원소들 탐색
    for(int j = 0; j < i; j++) {

        // j번째 원소가 i번째 원소보다 작으면서 i번째 dp가 j번째 dp+1 값보다 작은경우
        if(arr[j] < arr[i] && dp[i] < dp[j] + 1) {
            dp[i] = dp[j] + 1;  // j번째 원소의 +1 값이 i번째 dp가 된다.
        }
    }
}

 

 

'알고리즘' 카테고리의 다른 글

[알고리즘] 동적계획법 DP (Dynamic Programming)  (0) 2024.03.26