[PS] 최장 증가 부분 수열 (LIS)

2023. 5. 14. 13:53·Problem solving


최장 증가 부분 수열(Longest Increasing Subsequence) 문제는 주어진 수열에서 정렬된 가장 긴 부분 수열을 찾는 문제이다.

예를 들어 주어진 수열이 10, 20, 10, 30, 20, 50이라고 하면
찾을 수 있는 가장 긴 증가하는 부분수열은 10, 20, 30, 50이다.

LIS의 길이를 구하기 위한 알고리즘은 두 가지가 있다.


1. 시간 복잡도가  O(n^2)인 방법


이중 반복문으로 구현되므로 O(n^2)의 시간 복잡도를 요구하는 방법이다.
크기가 len(arr)인 dp테이블을 선언한다.
각 인덱스(i)를 순회하면서 가능한 LIS의 최대 길이를 채워 넣는다.
이때 최초 인덱스 부터 i 직전 인덱스 까지 값들 중
arr 값이 arr[i] 보다 작은 값들 중, dp 값이 최대인 경우를 찾아 val에 저장한다
val 값에 1을 더하여 dp[i]에 채워 넣는다.
dp 테이블을 끝까지 채운 후 저장된 값들 중 최대 값을 찾으면 LIS의 길이를 구할 수 있다.

# LIS : O(n^2)의 시간 복잡도
# 구현 방법 : dp 

import sys
n = int(input())
arr = list(map(int,sys.stdin.readline().split()))
dp = [0 for _ in range(n)]; dp[0] = 1
for i in range(n):
    val = 0  
    # arr[j] < arr[i]인 j들 d[j]의 최댓값을 저장하는 변수 
    for j in range(i-1, -1, -1):
        if arr[j] >= arr[i]: continue
        if val < dp[j]: val = dp[j]
    dp[i] = val + 1
print(max(dp))





2. 시간 복잡도가 O(nlogn)인 방법

bisect 라이브러리
정렬된 리스트에 원소를 삽입한 후에 다시 정렬할 필요 없도록 도와주는 라이브러리로 이진 탐색 알고리즘으로 구현되었기 때문에 O(logn)의 시간 복잡도를 가진다.



bisect.bisect_left(arr, x) : arr가 정렬되어 있다는 가정 하에 x값이 들어갈 가장 왼쪽 인덱스를 찾아서 반환해 준다.
bisect.bisect_right(arr, x) : arr가 정렬되어 있다는 가정 하에 x값이 들어갈 가장 오른쪽 인덱스를 찾아서 반환해 준다.

일단 빈 리스트에 arr[0]를 넣어서 선언한다 (dp = [arr[0]])
각 인덱스를 순회하면서
1. dp의 마지막 원소보다 arr[i]가 큰 경우 dp.append(arr[i]) : dp의 길이를 증가시킨다.
2. 그 외의 경우 dp 내부 원소 중  arr[i]가 들어갈 자리를 이진 탐색으로 찾아(dp는 정렬된 상태) 해당 인덱스에 값을 arr[i]로 바꾼다. (하지만 dp의 길이는 길어지지 않음)
# bisect_left로 인덱스를 찾으므로 비교 대상이 되는 값(직후 값)을 밀어내게 되므로 dp 리스트를 계속 가능한 가장 낮은 숫자로 업데이트 되게 된다.



이 방법의 핵심은
구하고자 하는 것(LIS의 길이)과 그 외의 것을 구분하는 것이다.
구하고자 하는 것은 LIS의 길이이지 LIS 자체가 아니므로,
중요한 것은 dp의 길이이고, dp에 저장된 값과 실제 LIS가 같은지 다른지는 중요하지 않다.
* LIS의 길이에 영향을 주는 요인은 LIS의 마지막 원소이다. 그러므로 다른 원소들은 바껴도 상관이 없다.
dp의 길이는 조건이 충족 되었을 때(dp 마지막 원소보다 arr[i]가 큰 경우)만 늘려 주고
* dp에 저장된 마지막 원소는, 성립하는 LIS의 마지막 원소와 같으므로, dp[-1] < arr[i]인 경우 LIS의 길이는 늘어나게 됨!
그 외의 경우에는 dp 리스트를 계속 가능한 낮은 숫자로 업데이트 하는 것이다.


단순하게 dp의 마지막 원소와 arr[i] 값을 비교만 하고, arr[i]가 dp에 들어갈 위치를 찾아 계속 업데이트해 주지 않는다면,
위와 같은 예시에서 제대로 작동하지 않게 된다.

# LIS : O(nlogn)의 시간 복잡도
# 구현 방법 : 이진 탐색 (bisect 라이브러리), dp

import sys
from bisect import bisect_left
n = int(input())
arr = list(map(int,sys.stdin.readline().split()))
dp = [arr[0]]
for i in range(1, len(arr)):
    if arr[i] > dp[-1]: dp.append(arr[i])
    else:
        idx = bisect_left(dp, arr[i])
        dp[idx] = arr[i]
print(len(dp))

# 중요한 것은 dp의 길이이고, dp에 현재 저장된 값과 실제 LIS가 같은지 다른지는 중요하지 않다

 

'Problem solving' 카테고리의 다른 글
  • [PS] CCW (Counter Clock Wise) 알고리즘
  • [PS] boj_14003 : 가장 긴 증가하는 부분 수열 5
  • [PS] boj_10942 : 팰린드롬?
  • [PS] Manacher 알고리즘 (Manacher's Algorithm)
vysryoo
vysryoo
  • vysryoo
    vysryoo
    vysryoo
  • 전체
    오늘
    어제
    • 분류 전체보기 (129)
      • Python (20)
      • Data structure (12)
      • Algorithm (14)
      • Operating system (18)
      • Programming language theory (12)
      • Computer architecture (6)
      • Softeware engineering (8)
      • Multicore (2)
      • Data Base (3)
      • Problem solving (24)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
vysryoo
[PS] 최장 증가 부분 수열 (LIS)
상단으로

티스토리툴바