파이썬 코딩테스트 강좌, 시간 복잡도 활용하기

알고리즘 문제풀이 및 시간 복잡도 이해를 위한 자세한 가이드

문제 설명

다음 코드를 기반으로 가장 긴 증가하는 부분 수열 (Longest Increasing Subsequence, LIS)을 찾는 문제를 해결해보세요.

여러 개의 숫자로 이루어진 정수 배열이 주어질 때, 이 배열에서 가장 긴 증가하는 부분 수열의 길이를 구하는 함수 length_of_lis(nums)를 작성하세요. 증가하는 부분 수열은 배열의 원소들이 원래 배열의 순서를 유지하며 증가하는 것을 의미합니다.

입력 예시:

nums = [10, 9, 2, 5, 3, 7, 101, 18]

출력 예시:

4  # LIS는 [2, 3, 7, 101] 또는 [10, 18] 등

문제 해결 과정

1. 시간 복잡도 이해하기

이 문제를 해결하기 위해 먼저 시간 복잡도를 고려해야 합니다. 기본적으로, 이 문제는 O(n^2) 시간 복잡도로 풀이될 수 있습니다. 하지만 O(n log n) 시간 복잡도로 해결하는 방법도 있으며, 이는 알고리즘의 효율성을 크게 향상시킬 수 있습니다.

2. 동적 프로그래밍을 활용한 풀이

가장 긴 증가하는 부분 수열을 구하는 동적 프로그래밍 접근 방식은 다음과 같습니다:

  • 각 원소에 대해 LIS의 길이를 추적하는 배열 dp를 생성합니다. 초기값은 모두 1로 설정합니다 (각 원소는 자기 자신으로서 증가하는 부분 수열을 형성하기 때문).
  • 각 원소를 기준으로 이전의 원소와 비교하여, 해당 원소가 더 크다면 dp[i]를 갱신합니다.
  • 최종적으로 dp 배열에서 최대값을 찾으면 LIS의 길이를 구할 수 있습니다.

3. 코드 구현

아래는 Python으로 구현한 코드입니다:

def length_of_lis(nums):
    if not nums:
        return 0

    dp = [1] * len(nums)  # 각 원소는 최소 1의 길이를 가짐
    for i in range(len(nums)):
        for j in range(i):
            if nums[i] > nums[j]:  # 증가하는 조건
                dp[i] = max(dp[i], dp[j] + 1)

    return max(dp)

4. 시간 복잡도 분석

위의 동적 프로그래밍 솔루션의 시간 복잡도는 O(n2)입니다. 이는 두 개의 중첩 반복문이 있기 때문입니다. 하지만, O(n log n) 방식으로 해결할 수 있는 방법도 있습니다. 이 방법에서는 이분 탐색을 활용하여 효율성을 끌어올릴 수 있습니다.

5. O(n log n) 접근 방식

O(n log n) 접근 방식은 이분 탐색을 사용합니다. 증가하는 수열을 추적하는 리스트를 유지하고, 각 원소에 대해 해당 리스트 내에서 적절한 위치를 찾습니다:

import bisect

def length_of_lis(nums):
    lis = []
    for num in nums:
        pos = bisect.bisect_left(lis, num)  # 이분 탐색으로 삽입 위치 찾기
        if pos == len(lis):
            lis.append(num)  # 새로운 최대값 추가
        else:
            lis[pos] = num  # 기존 값 대체
    return len(lis)

6. 예제 실행 및 결과 확인

아래와 같은 예제로 함수가 제대로 작동하는지 확인해보세요:

nums = [10, 9, 2, 5, 3, 7, 101, 18]
print(length_of_lis(nums))  # 출력: 4

7. 시간 복잡도 분석

O(n log n) 방식에서, bisect.bisect_left는 로그 시간에 작동하므로 전체 시간 복잡도는 O(n log n)입니다. 이는 큰 입력에서도 빠른 응답을 제공하므로 실제 코딩 테스트에서 유용하게 사용됩니다.

결론

이 글에서 다룬 내용은 파이썬을 사용한 알고리즘 문제 해결 방법과 시간 복잡도의 개념을 포함합니다. 처음에는 동적 프로그래밍을 통해 LIS 문제를 O(n2)로 해결하는 방법을 소개했으며, 이후 O(n log n) 방식으로 효율성을 개선하는 방법도 제시했습니다. 시간 복잡도를 이해하고 활용하는 것은 알고리즘 설계에서 매우 중요합니다. 앞으로도 다양한 문제를 해결하고, 시간 복잡도를 고려하는 연습을 계속하시길 바랍니다!