'''
이 문제는 주어진 정수 배열에서 가장 긴 증가하는 부분 수열(LIS)의 길이를 찾는 문제임
부분 수열이 연속되지는 않아도 되지만 순서는 유지해야 함
이진 탐색을 활용!

시간 복잡도: O(n log n)
        각 숫자마다 이진 탐색(O(log n))을 수행하므로 전체 O(n log n)

공간 복잡도: O(n)
        tails 배열이 최대 n의 공간을 사용함


Example 1의 경우를 들면
nums = [10,9,2,5,3,7,101,18]

| 단계  |  처리할 숫자   | 동작 설명                          | `tails` 배열 상태       |
|------|-------------|----------------------------------|-----------------------|
| 1    | 10          | 빈 배열에 10 추가                   | `[10]`                 |
| 2    | 9           | 10보다 작으므로 10 → 9로 교체         | `[9]`                  |
| 3    | 2           | 9보다 작으므로 9 → 2로 교체           | `[2]`                  |
| 4    | 5           | 2보다 크므로 5 추가                  | `[2, 5]`               |
| 5    | 3           | 5보다 작으므로 5 → 3로 교체           | `[2, 3]`               |
| 6    | 7           | 3보다 크므로 7 추가                  | `[2, 3, 7]`            |
| 7    | 101         | 7보다 크므로 101 추가                | `[2, 3, 7, 101]`       |
| 8    | 18          | 101보다 작으므로 101 → 18로 교체      | `[2, 3, 7, 18]`        |

최종 결과 : tails 배열의 길이 = 4 
최장 증가 부분 수열 예시 : [2, 3, 7, 18] 또는 [2, 3, 7, 101] 등이 가능함

'''

class Solution:
    def lengthOfLIS(self, nums: List[int]):

        # 각 길이의 부분 수열 중 가장 작은 마지막 값을 저장함
        tails = []  
        
        for num in nums:

            # num이 들어갈 적절한 위치를 이진 탐색으로 찾음
            index = bisect.bisect_left(tails, num) 

            if index == len(tails):

                # num이 tails의 모든 값보다 크면 새로운 길이의 부분 수열을 생성
                tails.append(num)
            else:
                
                # 기존 부분 수열의 마지막 값을 더 작은 num으로 갱신함
                # tails 배열의 의미는 tails[i] = 길이가 i+1인 부분 수열의 가장 작은 마지막 값을 유지하여 
                # 새로운 숫자가 추가되기 쉬워짐
                tails[index] = num   
        
        # tails의 길이가 최대 LIS 길이임
        return len(tails) 


