LIS(최장 증가 부분 수열) (1차원 DP부터 이분탐색)

2025. 4. 22. 13:17·Computer Science
728x90

LIS (Longest Increasing Subsequence) 란?

  • 어떤 수열에서 순서를 바꾸지 않고, 값이 점점 증가하는 부분 수열 중
    가장 긴 것의 길이를 구하는 문제입니다.

예시:

수열: [10, 20, 10, 30, 20, 50]  
→ LIS: [10, 20, 30, 50] → 길이 = 4

 

여기서 말하는 “부분 수열”은
연속된 값이 아니어도 되고, 원래 순서를 유지하기만 하면 됩니다.

 

 

 


 

이 문제, 왜 중요한가요?

 

  • LIS는 1차원 DP의 대표 문제입니다.
  • 또한, 이분 탐색을 접목한 시간 최적화 기법까지 등장하는 알고리즘 진화 사례이기도 합니다.
  • 실무에서도 아래와 같은 패턴으로 자주 등장합니다:
실전 분야 활용 사례
주식 분석 오름차순으로 수익을 최대화하는 거래 흐름 탐색
게임 점수/레벨 분석 상승하는 상태 유지 구간 추적
문자열 비교 사전순 증가 수열 찾기 (ex: 알파벳 정렬)
연속 데이터 분석 최소 편집으로 증가 패턴 만들기

 

 

브루트포스 방식은 어떤가요? (O(2ⁿ))

가장 단순하게 생각하면, 가능한 모든 부분 수열을 만들고,

그중에서 오름차순이며 가장 긴 것을 찾을 수 있습니다.

 

하지만 이 경우 시간복잡도는 O(2ⁿ) - 사실상 사용이 불가능합니다.

 따라서 우리는 DP(동적 프로그래밍)을 사용해야 합니다.

 


 

DP로 푸는 방식 – O(n²)

  • dp[ i ] : 수열의 i번째까지 고려했을 때 i를 끝으로 하는 LIS의 최대 길이
arr = [10, 20, 10, 30, 20, 50]
dp  = [1,  2,  1,  3,  2,  4]

 

점확식

dp[i] = max(dp[j] + 1)  
(단, j < i 이고 arr[j] < arr[i])

 

즉, 이전에 본 원소들 중, 나보다 작았던 것들 중에서 

가장 길었던 부분 수열의 길이에 +1 해주는 방식

 


 

Python 구현 (O(n²))

def lis_dp(arr):
    n = len(arr)
    dp = [1] * n  # 초기: 모두 자기 자신 하나로 LIS 가능

    for i in range(n):
        for j in range(i):
            if arr[j] < arr[i]:
                dp[i] = max(dp[i], dp[j] + 1)

    return max(dp)

 

 

기본 예제

arr = [10, 20, 10, 30, 20, 50]
→ LIS 길이 = 4 ([10, 20, 30, 50])
arr = [3, 5, 7, 2, 8, 6, 10]
→ LIS 길이 = 5 ([3, 5, 7, 8, 10] or [2, 6, 8, 10] 등)

이분 탐색 적용 (O(n log n))

실제 LIS를 구하는 게 아니라, 길이만 구한다

LIS를 만들 수 있게 가장 작은 수로 리스트를 유지

arr = [10, 20, 10, 30, 20, 50]

temp = []
→ temp = [10]
→ 20 > 10 → append → [10, 20]
→ 10 → lower_bound = 0 → replace → [10, 20]
→ 30 > 20 → append → [10, 20, 30]
→ 20 → lower_bound = 1 → replace → [10, 20, 30]
→ 50 → append → [10, 20, 30, 50]

이 리스트는 실제 LIS는 아니지만 길이는 같음

 

Python 구현 (O(n log n))

import bisect

def lis_binary(arr):
    result = []
    for num in arr:
        idx = bisect.bisect_left(result, num)
        if idx == len(result):
            result.append(num)
        else:
            result[idx] = num
    return len(result)

 


 

이분 탐색 방식과 DP 방식 비교

항목 DP 방식 이분 탐색 방식
구현 난이도 쉽다 약간 복잡
시간복잡도 O(n²) O(n log n)
실제 LIS 출력 가능 (역추적 필요) 따로 구현해야 함
실전 추천 n ≤ 1,000 n ≤ 1,000,000

 


 

LIS 전체 수열 복원 (DP 방식 + 역추적)

def get_lis(arr):
    n = len(arr)
    dp = [1] * n
    prev = [-1] * n

    for i in range(n):
        for j in range(i):
            if arr[j] < arr[i] and dp[j] + 1 > dp[i]:
                dp[i] = dp[j] + 1
                prev[i] = j

    # LIS 길이와 끝 인덱스
    length = max(dp)
    idx = dp.index(length)

    # 역추적
    lis = []
    while idx != -1:
        lis.append(arr[idx])
        idx = prev[idx]

    return list(reversed(lis))

 


요약

  • LIS는 1차원 수열에서의 가장 긴 증가 패턴을 찾는 문제
  • DP 방식 (O(n²))은 개념 이해와 수열 복원에 유리
  • 이분 탐색 방식(O(n log n))은 속도 최적화에 강력
  • 실전에서는 문제의 크기와 목적(LIS 길이만? 전체 수열도?) 에 따라 방법 선
728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'Computer Science' 카테고리의 다른 글

수열 편집, 재배치, 중첩 문제에서 어떻게 LIS를 활용하는가?  (0) 2025.04.22
LDS(최장 감소 부분 수열)  (0) 2025.04.22
LCS(최장 공통 부분 수열 & 문자열 DP 문제)  (0) 2025.04.22
최소 신장 트리(MST) (Kruskal vs Prim 알고리즘 비교)  (0) 2025.04.22
플로이드–워셜 알고리즘 (모든 노드 간 최단 경로 구하기)  (0) 2025.04.21
'Computer Science' 카테고리의 다른 글
  • 수열 편집, 재배치, 중첩 문제에서 어떻게 LIS를 활용하는가?
  • LDS(최장 감소 부분 수열)
  • LCS(최장 공통 부분 수열 & 문자열 DP 문제)
  • 최소 신장 트리(MST) (Kruskal vs Prim 알고리즘 비교)
Balang
Balang
음악 전공생의 개발일지
  • Balang
    Balang
    Balang
  • 전체
    오늘
    어제
  • 반응형
    • All Post (160)
      • python (47)
        • selenium (4)
        • algorithm (10)
        • Django (6)
        • Pandas | Numpy (22)
      • SQL (9)
      • Data Engineer (36)
      • Data Scientist (3)
      • Data Analysis (11)
      • Computer Science (36)
      • Why? (16)
      • 마음가짐 (2)
  • 인기 글

  • 최근 댓글

  • 최근 글

  • 250x250
  • hELLO· Designed By정상우.v4.10.3
Balang
LIS(최장 증가 부분 수열) (1차원 DP부터 이분탐색)
상단으로

티스토리툴바