원형 진열대에서 가장 긴 중복 없는 구간
자바스크립트 코딩테스트 문제로 circular-sliding-window 주제를 연습해보세요. 난이도는 medium이며, 브라우저에서 바로 JavaScript로 풀이를 실행할 수 있습니다.
같은 상품명이 다시 나오기 전까지, 원형 진열대를 따라 연속으로 볼 수 있는 가장 긴 구간 길이를 구하는 문제입니다.
문제 설명
상품명이 순서대로 담긴 배열 products가 주어집니다. 이 배열은 원형 진열대를 나타내므로, 마지막 다음에는 다시 첫 번째 상품으로 이어집니다.
이때, 진열대를 따라 연속해서 이동하며 본 상품들 중 같은 이름이 두 번 이상 나오지 않는 가장 긴 구간의 길이를 반환하는 solution 함수를 작성하세요.
단, 한 바퀴를 넘겨 같은 칸을 두 번 이상 세는 것은 허용하지 않으므로, 선택한 구간의 길이는 products.length를 초과할 수 없습니다.
예를 들어 products = ["A", "B", "C", "A"]라면, 원형으로 이어 보더라도 A가 두 번 등장하는 순간 더 이상 포함할 수 없으므로 중복 없는 최장 구간 길이는 3입니다.
제한사항
1 <= products.length <= 200,000- 각 상품명은 길이 1 이상 20 이하의 문자열입니다.
- 상품명은 대소문자를 구분합니다.
- 배열은 원형으로 이어져 있습니다.
- 선택한 연속 구간의 길이는
products.length를 넘을 수 없습니다.
예시
- 입력:
["A", "B", "C", "A"]→ 출력:3 - 입력:
["apple", "banana", "apple", "orange"]→ 출력:3 - 입력:
["x", "x", "x"]→ 출력:1 - 입력:
["p", "q", "r", "s"]→ 출력:4
두 번째 예시에서는 banana → apple → orange 길이 3 구간이 가능하지만, 그보다 길게 가면 apple이 다시 등장해 중복이 생깁니다.
힌트
- 원형 배열은 시작점을 모든 위치로 바꿔 보려 하면 비효율적입니다.
- 배열을 두 번 이어 붙인 것처럼 생각하면 원형 구간을 일반 연속 구간처럼 다룰 수 있습니다.
- 다만 윈도우 길이가 원래 배열 길이보다 길어지면 한 바퀴를 넘긴 것이므로 허용하면 안 됩니다.
- 현재 윈도우 안에서 상품이 중복되지 않게 관리하면 됩니다.
해설
이 문제는 원형 배열 + 중복 없는 최장 연속 구간 문제입니다.
원형이라는 점만 빼면, 일반 배열에서 중복 없는 가장 긴 부분 배열을 찾는 전형적인 슬라이딩 윈도우 문제와 비슷합니다. 문제는 구간이 배열 끝을 넘어 앞부분으로 이어질 수 있다는 점입니다.
이를 처리하는 쉬운 방법은 배열을 실제로 두 번 복사하지 않더라도, 인덱스를 0부터 2n - 1까지 움직이며 products[index % n]로 접근해 배열을 두 번 이어 붙인 것처럼 보는 것입니다.
그다음 투 포인터 윈도우를 유지합니다.
- 오른쪽 포인터를 한 칸씩 늘리며 새 상품을 포함합니다.
- 이미 윈도우 안에 같은 상품이 있다면, 중복이 사라질 때까지 왼쪽 포인터를 줄입니다.
- 또한 윈도우 길이가
n을 넘으면 한 바퀴를 초과한 것이므로 왼쪽 포인터를 줄여 길이를 다시n이하로 맞춥니다. - 각 단계에서 가능한 최대 길이를 갱신합니다.
왜 이 방식이 맞을까요?
- 원형에서 가능한 모든 연속 구간은, 길이
n이하라는 조건만 붙이면products + products위의 어떤 연속 구간으로 표현할 수 있습니다. - 따라서 두 배 길이 구간만 훑어도 원형 구간 후보를 모두 확인할 수 있습니다.
- 각 상품은 윈도우에 들어오고 나가는 일이 많아야 한 번씩이므로 전체 시간 복잡도는
O(n)입니다.
예를 들어 products = ["A", "B", "C", "A"]를 보겠습니다.
A, B, C까지는 모두 달라 길이 3- 다음
A를 넣으면 처음A와 중복되므로 왼쪽을 줄여 중복을 없앱니다. - 원형으로 이어지는 구간까지 모두 확인해도
A를 두 번 포함하지 않는 최장 길이는 3입니다.
구현에서는 Set으로 현재 윈도우에 있는 상품을 관리하면 직관적으로 풀 수 있습니다.
function solution(products) {
const n = products.length;
const seen = new Set();
let left = 0;
let answer = 0;
for (let right = 0; right < n * 2; right += 1) {
const current = products[right % n];
while (seen.has(current) || right - left + 1 > n) {
seen.delete(products[left % n]);
left += 1;
}
seen.add(current);
answer = Math.max(answer, right - left + 1);
}
return answer;
}
이 방식은 길이 제한과 중복 제거를 동시에 처리하므로, 큰 입력도 충분히 빠르게 해결할 수 있습니다.
코드 작성
starter code를 바탕으로 함수를 완성한 뒤 예제 테스트를 실행해보세요.
커스텀 테스트
함수 인자를 JSON 배열 형태로 입력하세요. 예: [3, 5], [[1, 2, 3]]
실행 결과
아직 실행하지 않았습니다.
댓글
문제 풀이 아이디어, 질문, 반례를 자유롭게 나눠보세요.