dp[i]를 dp[j] + 1로 업데이트 한다. (즉, dp[i] = max(dp[i], dp[j] + 1))
def lengthOfLIS(self, nums: List[int]) -> int:
"""
DP (TC: O(n^2))
- i 번째 원소를 볼 때, 0 ~ i - 1 번째의 값을 보면서, 가장 큰 값에 + 1
- dp[i] = nums[i]가 포함되는 LIS의 길이
"""
n = len(nums)
dp = [1] * n
for i in range(n):
for j in range(i):
if nums[i] > nums[j] and dp[i] < dp[j] + 1:
dp[i] = dp[j] + 1
return max(dp)
Idea 2: Greedy + Binary Search (O(n log(n)) Time) 📌
이때, lis가 실제 LIS인 것은 아니며, len(lis)가 가능한 LIS의 길이일 뿐이다.
def lengthOfLIS(self, nums: List[int]) -> int:
def find_left(lis, num):
lo, hi = 0, len(lis) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if lis[mid] < num:
lo = mid + 1
else:
hi = mid
return lo
lis = []
for num in nums:
# (1) lis가 비어있거나 (2) lis의 마지막 값보다 현재 원소가 큰 경우에는 append
if not lis or lis[-1] < num:
lis.append(num)
# 아니라면 lis에서 num이 들어갈 위치(= num보다 크거나 같은 첫번째 원소) 구하고 덮어쓰기
else:
idx = find_left(lis, num)
lis[idx] = num
return len(lis)
Approach
DP 문제로 알려진 LIS 문제이다. 다른 방법으로도 푸는 방법을 알아두자.
Idea 1: DP (
O(n^2)
Time)dp[i]
=nums[i]
가 포함되는 LIS의 길이nums[i]
를 볼 때,i
이전의 인덱스인j
를 확인하며nums[i]
>nums[j]
이고dp[i]
<dp[j] + 1
라면dp[i]
를dp[j] + 1
로 업데이트 한다. (즉,dp[i] = max(dp[i], dp[j] + 1)
)Idea 2: Greedy + Binary Search (
O(n log(n))
Time) 📌다음의 follow-up 조건을 고려하려면 binary search를 적용해야 한다.
nums
를 순회하면서 가능한 모든 LIS를 구성한다면 성능이 좋지 않을 것이지만, 단 하나의 리스트lis
만 가지고 LIS의 길이를 구할 수는 있다.이는 현재 확인하고 있는 원소
num
이lis
의 마지막 원소보다 크지 않다면(lis[-1] >= num
),num
을lis
에 덮어씌움으로써 가능하다.lis
를 구성한다.lis
가 비어있거나 (2) 현재 원소num
이lis
의 마지막 원소보다 크다면(lis[-1] < num
)lis
에 모은다.그러나 현재 원소
num
이lis
의 마지막 원소보다 크지 않다면(lis[-1] >= num
), 현재 원소num
을lis
에 덮어쓴다.lis
에 덮어쓸 위치를 구하려면,lis
에서num
이상인 값 중에서 가장 작은 값의 위치를 찾으면 된다.lis
는 increasing 순서로 구성되어 있으므로 binary search를 사용할 수 있다.이때,
lis
가 실제 LIS인 것은 아니며,len(lis)
가 가능한 LIS의 길이일 뿐이다.Complexity
O(n^2)
/ (idea 2)O(n log(n))
O(n)