2차원 정수 배열 intervals가 주어집니다.
intervals[i] = [li, ri, weighti]이며,
i는 위치 li에서 시작해서 ri에서 끝납니다.i의 가중치는 weighti입니다.서로 겹치지 않는 구간을 최대 4개까지 선택할 수 있습니다.
선택한 구간들의 점수(score) 는 선택한 모든 구간의 가중치 합으로 정의됩니다.
가능한 선택 중 점수가 최대가 되도록 구간을 선택하고, 그 구간들의 원래 intervals 배열에서의 인덱스들을 담은 배열을 반환하세요.
최대 점수를 만드는 방법이 여러 개라면, 그중 사전순(lexicographical order)으로 가장 작은 인덱스 배열을 반환해야 합니다.
두 구간이 어떤 점도 공유하지 않을 때 서로 겹치지 않는다고 합니다. 특히 한 구간의 끝점과 다른 구간의 시작점이 같더라도 두 구간은 겹치는 것으로 간주됩니다.
즉,
[1, 3]과 [4, 5] → 겹치지 않음
[1, 3]과 [3, 5] → 겹침
핵심은 최대 4개이므로 반드시 4개를 고를 필요는 없고, ri < lj인 경우에만 두 구간을 함께 선택할 수 있다는 것입니다.
입력: intervals = [[1,3,2],[4,5,2],[1,5,5],[6,9,3],[6,7,1],[8,9,1]]
출력: [2,3]
설명:
인덱스가 2, 3인 구간을 선택할 수 있습니다.
각 구간의 가중치는 각각 5, 3이며, 총 점수는 8입니다.
class Solution:
def maximumWeight(self, intervals: List[List[int]]) -> List[int]:
N = len(intervals)
intervals = [(*interval, i) for i, interval in enumerate(intervals)]
intervals.sort()
@cache
def dp(index: int, left: int) -> Tuple[int, Tuple[int]]:
if index >= N or left == 0:
return 0, []
current = intervals[index]
next_pos = bisect_left(intervals, current[1] + 1, key=lambda k: k[0])
skip_weight, skip_list = dp(index + 1, left)
take_weight, take_list = dp(next_pos, left - 1)
take_weight += current[2]
take_list = sorted([*take_list, current[3]])
take_list = tuple(take_list)
skip = (skip_weight, tuple(skip_list))
take = (take_weight, tuple(take_list))
if skip_weight != take_weight:
return max(skip, take)
if skip_list < take_list:
return skip
else:
return take
return dp(0, 4)[1]
매우 전형적인 Take or Not Take 형태의 DP 문제이다.
현재 구간을 선택하지 않는 경우에는 dp(index + 1, left)를, 선택하는 경우에는 현재 구간과 겹치지 않는 다음 구간을 찾아 dp(next_pos, left - 1)를 계산한다.
다만 일반적인 구간 DP와 달리 최종 가중치뿐만 아니라 지금까지 선택한 원본 인덱스들을 함께 관리해야 한다는 특징이 있다.
또한 최대 가중치가 같은 경우에는 선택한 인덱스 배열 중 사전순으로 더 작은 결과를 반환해야 하므로, DP의 반환값에 가중치와 인덱스 목록을 함께 저장하여 비교한다.
이 부분을 제외하면 전형적인 Weighted Interval Scheduling에 선택 가능한 구간의 개수를 최대 4개로 제한한 형태라서, 전체적인 구조 자체는 크게 어렵지 않은 문제인 듯하다.