배열 a의 부분 수열 중 가장 긴 스타 수열의 길이를 구한다.
스타 수열은 인접한 두 원소씩 묶었을 때 다음을 만족해야 한다.
예를 들어 공통 숫자를 0으로 정했다면, 선택하는 모든 쌍은 (0, 다른 수) 또는 (다른 수, 0) 형태여야 한다.
공통으로 포함될 숫자를 star라고 고정해 보자.
star를 포함하면서 두 원소가 다른 인접 구간만 스타 수열의 한 쌍으로 사용할 수 있다.
a[i] != a[i + 1] 이고
a[i] == star 또는 a[i + 1] == star
이 조건을 만족하는 인접 구간을 간선으로 생각한다. 같은 위치를 공유하는 두 간선은 동시에 고를 수 없으므로, 각 star에 대해 서로 겹치지 않는 간선을 최대한 많이 고르면 된다.
간선의 시작 위치를 앞에서부터 확인하면서, 이전에 고른 간선과 겹치지 않는 첫 간선을 선택하는 그리디가 최적이다.
후보 숫자마다 전체 배열을 순회하면 최악의 경우 시간이 너무 오래 걸릴 수 있다.
대신 배열의 이웃한 두 원소 a[i], a[i + 1]를 한 번만 확인한다.
a[i]를 공통 숫자로 하는 경우와 a[i + 1]를 공통 숫자로 하는 경우에 모두 사용할 수 있다.따라서 간선의 시작 위치 i를 두 숫자의 목록에 각각 추가한다.
[5, 2, 3, 3, 5, 3]
인덱스 0의 (5, 2) -> 5와 2의 간선 목록에 0 추가
인덱스 1의 (2, 3) -> 2와 3의 간선 목록에 1 추가
인덱스 2의 (3, 3) -> 무시
...
각 간선은 최대 두 번만 저장되므로, 전체 처리량은 선형이다.
def solution(a):
length = len(a)
# a의 원소 범위는 0 이상 length 미만이다.
edges_by_value = [[] for _ in range(length)]
# 서로 다른 이웃 원소로 이루어진 간선을 두 값의 후보 목록에 저장한다.
for index in range(length - 1):
left = a[index]
right = a[index + 1]
if left == right:
continue
edges_by_value[left].append(index)
edges_by_value[right].append(index)
answer = 0
for edges in edges_by_value:
pair_count = 0
last_selected_edge = -2
# edges는 인덱스 순서대로 추가되어 이미 정렬된 상태다.
for edge_start in edges:
# edge_start와 edge_start + 1 간선이 이전 간선과
# 같은 원소를 공유하지 않을 때만 선택할 수 있다.
if edge_start > last_selected_edge + 1:
pair_count += 1
last_selected_edge = edge_start
answer = max(answer, pair_count * 2)
return answer
간선 시작 위치가 i라면, 이 간선은 a[i]와 a[i + 1]을 하나의 쌍으로 선택한다.
따라서 시작 위치가 연속된 두 간선 i, i + 1은 a[i + 1]을 공통으로 사용하므로 동시에 고를 수 없다.
if edge_start > last_selected_edge + 1:
이 조건은 현재 간선이 이전에 선택한 간선과 원소를 공유하지 않는지 확인한다.
예를 들어 star = 5이고 가능한 간선 시작 위치가 [0, 3]이라면, 두 간선은 겹치지 않으므로 2쌍을 선택할 수 있다. 결과 스타 수열의 길이는 2 * 2 = 4다.
N을 배열 a의 길이라고 하자.
인접한 원소 확인: O(N)
저장된 간선 순회: 각 간선은 최대 두 값의 목록에만 들어가므로 O(N)
시간 복잡도: O(N)
공간 복잡도: O(N)
스타 수열의 공통 숫자를 기준으로, 그 숫자를 포함하는 서로 다른 이웃 원소 쌍을 간선으로 바꿔 생각하면 된다. 각 숫자에 대해 겹치지 않는 간선을 앞에서부터 고르는 그리디를 적용하면, 후보마다 전체 배열을 다시 순회하지 않고도 가장 긴 스타 수열을 구할 수 있다.