JavaScript에서 배열에 같은 값이 여러 번 등장할 때, 어떤 요소들이 중복되었는지 한눈에 파악해야 하는 경우가 자주 있습니다. 이번 글에서는 두 번 이상 등장하는 모든 요소를 추출하는 함수를 작성하는 방법을 알아보겠습니다.
문제 정의
숫자로 이루어진 배열을 입력받아, 그중 한 번보다 많이 등장하는 요소들만 모은 새로운 배열을 반환하는 JavaScript 함수를 만들어야 합니다.
예를 들어 다음과 같은 배열이 있다고 가정해 보겠습니다.
const arr = [1, 3, 4, 3, 5, 4, 6, 8, 8];
이 경우 함수가 반환해야 할 결과 배열은 다음과 같습니다.
const output = [3, 4, 8];
배열에서 3, 4, 8은 각각 두 번씩 등장하기 때문에 결과에 포함되고, 나머지 요소들은 한 번만 등장하므로 제외됩니다.
해결 방법: 해시 맵으로 등장 횟수 추적하기
가장 효율적인 접근 방식은 객체(해시 맵)를 사용해 각 요소의 등장 횟수를 기록하는 것입니다. 요소가 처음 발견되면 카운트를 1로 설정하고, 이미 존재하는 요소라면 카운트를 증가시킵니다. 이때 카운트가 정확히 1에서 2로 바뀌는 시점에 해당 요소를 결과 배열에 추가하면, 중복 요소가 여러 번 저장되는 것을 방지할 수 있습니다.
전체 코드는 다음과 같습니다.
const arr = [1, 3, 4, 3, 5, 4, 6, 8, 8];
const findDuplicates = (arr = []) => {
let map = {};
let res = [];
for(let i = 0; i < arr.length; i++) {
if(map[arr[i]]) {
if(map[arr[i]] === 1) {
res.push(arr[i]);
}
map[arr[i]] = map[arr[i]] + 1;
} else {
map[arr[i]] = 1;
};
};
return res;
};
console.log(findDuplicates(arr));실행 결과
위 코드를 실행하면 콘솔에 다음과 같은 출력이 나타납니다.
[3, 4, 8]
코드 동작 원리 살펴보기
이 알고리즘의 핵심 로직을 단계별로 정리하면 다음과 같습니다.
1단계: 빈 객체 map과 결과를 담을 빈 배열 res를 초기화합니다.
2단계: 반복문으로 배열의 각 요소를 순회하면서, 해당 요소가 map에 이미 존재하는지 확인합니다.
3단계: 존재하지 않으면 카운트를 1로 설정하고, 존재하면서 아직 결과에 추가되지 않았다면(카운트가 1일 때) 결과 배열에 push한 뒤 카운트를 증가시킵니다.
4단계: 모든 순회가 끝나면 중복 요소만 담긴 배열을 반환합니다.
성능 분석
이 방식의 시간 복잡도는 O(n)입니다. 배열을 한 번만 순회하면 되고, 객체에서의 조회와 삽입은 평균적으로 상수 시간(O(1))이 걸리기 때문입니다. 공간 복잡도 역시 최악의 경우 모든 요소를 저장해야 하므로 O(n)입니다. 따라서 대용량 배열에서도 안정적으로 동작하는 효율적인 솔루션입니다.