코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기

2025.12.29·수정 2026.09.13·약 9분·작성: 해비·블로그 소개

주요 포인트 한눈에 보기

includes()로 교집합은 구했지만 입력이 3만 개까지 커질 때 시간 초과가 걱정되는 JavaScript 학습자를 위한 풀이입니다. 두 배열을 숫자 기준으로 정렬한 뒤 투 포인터로 한 번만 훑는 이유, 정렬 비용까지 포함한 복잡도, 원본 배열 변경과 중복값 가정을 예제로 검증합니다.

문제

A, B 두 개의 집합이 주어지면 두 집합의 공통 원소를 추출하여 오름차순으로 출력하는 프로그램을 작성하세요.

입력 설명
첫 번째 줄에 집합 A의 크기 N(1 ≤ N ≤ 30,000)이 주어집니다.
두 번째 줄에 N개의 원소가 주어집니다. 원소는 중복되지 않습니다.
세 번째 줄에 집합 B의 크기 M(1 ≤ M ≤ 30,000)이 주어집니다.
네 번째 줄에 M개의 원소가 주어집니다. 원소는 중복되지 않습니다.
각 집합의 원소는 1,000,000,000 이하의 자연수입니다.

출력 설명
두 집합의 공통 원소를 오름차순으로 정렬하여 출력합니다.

입력 예제
5
1 3 9 5 2
5
3 2 5 7 8

출력 예제
2 3 5

내가 푼 방식

공통 원소 추출 원인 진단: 두 배열 정렬 i/j 비교 같으면 추가 흐름

한 배열을 순회하면서 다른 배열에 포함되어 있는지 includes()로 확인하는 방식입니다.
공통 원소를 배열에 모은 뒤, 마지막에 정렬합니다.

function solution(arr1, arr2) {
    let arr = [];

    arr1.forEach((item) => {
        if (arr2.includes(item)) {
            arr.push(item);
        }
    });

    return arr.sort((a, b) => a - b);
}

왜 이 방식이 아닌가

includes()는 내부적으로 배열을 처음부터 끝까지 탐색합니다.
따라서 이 방식은 배열 크기가 커질수록 성능이 급격히 떨어집니다.

이 문제에서는 배열의 길이가 최대 30,000까지 가능하므로,
최악의 경우 O(N × M)에 가까운 시간이 걸릴 수 있습니다.
코딩 테스트에서는 이런 방식이 시간 초과로 이어질 가능성이 높습니다.

정답

공통 원소 추출 해결 체크리스트: 동일 값 처리 중복 처리 기준 포인터 증가 흐름

두 배열을 먼저 오름차순으로 정렬한 뒤, 투 포인터 방식으로 공통 원소를 찾습니다.

function solution(arr1, arr2) {
    let answer = [];
    arr1.sort((a, b) => a - b);
    arr2.sort((a, b) => a - b);

    let p1 = 0;
    let p2 = 0;

    while (p1 < arr1.length && p2 < arr2.length) {
        if (arr1[p1] === arr2[p2]) {
            answer.push(arr1[p1]);
            p1++;
            p2++;
        } else if (arr1[p1] < arr2[p2]) {
            p1++;
        } else {
            p2++;
        }
    }

    return answer;
}

정답 해설

두 배열을 각각 오름차순으로 정렬하면, 가장 작은 값부터 차례대로 비교할 수 있는 상태가 됩니다.
이 상태에서 두 포인터를 사용하면 불필요한 비교 없이 공통 원소만 골라낼 수 있습니다.

arr1[p1]과 arr2[p2]가 같으면 공통 원소이므로 결과에 추가하고,
두 포인터를 모두 한 칸씩 이동합니다.

값이 다를 경우에는 더 작은 값을 가진 쪽 포인터만 이동합니다.
이렇게 하면 이미 비교가 끝난 값은 다시 볼 필요가 없습니다.

공통 원소 추출 전에 투 포인터 기본 이동 방식을 먼저 잡고 싶다면 JS 투 포인터 배열 합치기를 함께 복습하면 좋습니다.

두 배열 공통 원소 문제의 핵심이 되는 포인터 이동 방식은 JS 연속 구간 합 투 포인터에서 기본 패턴으로 정리됩니다.

예제 디테일

기본 예제

console.log(solution([1, 3, 9, 5, 2], [3, 2, 5, 7, 8]));
// [2, 3, 5]

동작 설명: 두 배열을 정렬한 뒤, 작은 값부터 하나씩 비교합니다.
값이 같은 경우에만 결과 배열에 추가됩니다.

메커니즘 해설: 포인터는 한 방향으로만 이동하고 각 비교에서 적어도 하나가 전진합니다. 같은 원소가 여러 번 비교될 수는 있지만 총 포인터 이동 횟수는 두 배열 길이의 합 이하입니다.

실수 포인트: 값이 같으면 두 포인터를 함께 이동해 이미 처리한 두 원소를 건너뜁니다. 반복문에서 어느 포인터도 움직이지 않는 분기가 있으면 무한 루프가 발생할 수 있습니다.

응용 예제

console.log(solution([1, 2, 4, 6, 8], [2, 4, 6, 8, 10]));
// [2, 4, 6, 8]

동작 설명: 두 배열 모두 정렬되어 있으므로
포인터는 같은 값에서만 동시에 이동합니다.

메커니즘 해설: 중복이 없는 집합 조건 덕분에
결과에는 동일한 값이 한 번씩만 추가됩니다.

실수 포인트: 문제 조건에 “중복 없음”이 없다면
중복 처리 로직을 별도로 고려해야 합니다.

오류 예제

console.log(solution([3, 1, 5], [2, 3]));
// 정렬되지 않은 입력

동작 설명: 입력이 정렬되어 있지 않으면
포인터 비교 순서가 의미를 잃습니다.

메커니즘 해설: 투 포인터는 “앞에 있는 값이 더 작다”는
전제가 반드시 필요합니다.

실수 포인트: 이 문제는 정렬이 보장되지 않으므로
반드시 정렬 후에 투 포인터를 적용해야 합니다.

복잡도·입력 조건·검증 체크리스트

includes()를 각 원소마다 호출하는 첫 풀이는 최악의 경우 N×M번에 가까운 비교가 필요합니다. 정답 풀이는 두 배열 정렬에 O(N log N + M log M), 두 포인터 순회에 O(N+M)이 듭니다. 이미 정렬된 입력이라면 순회 부분만 O(N+M)이지만, 이 문제의 원본 입력은 정렬을 보장하지 않으므로 전체 복잡도에서 정렬 비용을 빼면 안 됩니다.

Array.prototype.sort()는 원본 배열을 제자리에서 바꾸며, 숫자를 오름차순으로 정렬하려면 (a, b) => a - b 비교 함수를 전달해야 합니다. 호출자가 원본을 계속 써야 한다면 [...arr1].sort(...)처럼 복사본을 정렬하세요. 자세한 동작은 MDN Array.prototype.sort() 문서에서 확인할 수 있습니다.

console.assert(JSON.stringify(solution([1, 3, 9, 5, 2], [3, 2, 5, 7, 8])) === '[2,3,5]');
console.assert(JSON.stringify(solution([], [1, 2])) === '[]');
console.assert(JSON.stringify(solution([4], [4])) === '[4]');

문제는 각 집합 안의 원소가 중복되지 않는다고 명시합니다. 중복을 허용하면 현재 코드는 두 배열에 존재하는 횟수의 최솟값만큼 결과에 넣는 다중집합 교집합처럼 동작하므로, ‘고유값만 한 번’이 목표라면 별도의 중복 제거 규칙이 필요합니다.

결과 원소 수를 K라고 하면 정답 배열에는 O(K) 공간이 필요합니다. 원본 보존을 위해 두 배열을 복사해 정렬하면 여기에 O(N+M) 복사 공간이 추가됩니다. 반대로 현재 코드처럼 제자리 정렬하면 복사 공간은 줄지만 호출 뒤 입력 순서가 달라집니다. 어느 선택이 맞는지는 함수 계약에 포함해야 합니다.

정리: 먼저 입력의 정렬 여부와 중복 정책을 확인하고, 정렬 비용·원본 변경 여부까지 설명할 수 있어야 완성된 풀이입니다. 이 조건을 적은 뒤 포인터가 한 방향으로만 이동한다는 불변식을 제시하면 시간 복잡도 설명도 명확해집니다.

풀이 전에 확인할 순서

  1. 입력값과 출력값을 한 문장으로 다시 적습니다.
  2. 반복할 대상과 비교·저장할 값을 정합니다.
  3. 필요한 자료구조와 시간복잡도를 예상합니다.
  4. 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기

정답 코드를 바로 따라 쓰기보다, 본문에서 값이 갱신되는 조건과 반복 범위를 먼저 찾으세요. 반복 한 번마다 반드시 유지되어야 하는 값이 무엇인지 적으면 풀이의 중심 변수를 고르기 쉽습니다.

테스트 확인

  • 가능한 가장 작은 입력
  • 같은 값이나 문자가 반복되는 입력
  • 정답이 처음 또는 마지막 위치에서 결정되는 입력
  • 입력 제한에 가까운 경우의 실행 시간

확인 결과: 본문의 예제뿐 아니라 위 경계 사례에서도 예상값과 실제 출력이 같아야 풀이가 완료됩니다.

이 글이 도움이 되었나요?

조회 중

코딩테스트 JavaScript 학습 순서

필수 49개 · 전체 49개

읽음 기록 관리

전체 과정 목차 (49개)
  1. 필수 길잡이 · 코딩테스트 JS 자료구조 로드맵: 배열, 해시, 스택, 투 포인터 순서
  2. 필수 학습 · 세 수 중 최솟값 JavaScript 조건문 풀이 정리
  3. 필수 학습 · 삼각형 판별하기 JavaScript 풀이
  4. 필수 학습 · 연필 개수 JavaScript 풀이
  5. 필수 학습 · 1부터 N까지 합 출력하기 JavaScript 풀이
  6. 필수 학습 · 최솟값 구하기 JavaScript 풀이|배열 순회와 비교 갱신 원리
  7. 필수 학습 · 홀수 JavaScript 풀이: 조건 판별과 결과 처리 정리
  8. 필수 학습 · 10부제 JavaScript 풀이: 끝자리 비교로 위반 차량 수 세기
  9. 필수 학습 · A를 #으로 JavaScript 풀이: 문자열 순회와 치환
  10. 필수 학습 · 문자 찾기 JavaScript 풀이: 문자열 순회로 개수 세기
  11. 필수 학습 · 대문자 찾기 JavaScript 풀이
  12. 필수 학습 · 대문자로 통일 JavaScript 풀이
  13. 필수 학습 · 대소문자 변환 JavaScript 풀이
  14. 필수 학습 · 일곱 난쟁이 JavaScript 풀이: 두 명을 제외하는 완전탐색
  15. 필수 학습 · 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기
  16. 필수 학습 · 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기
  17. 필수 학습 · 코딩테스트 JS 스택 풀이: 괄호문자 제거하기
  18. 필수 학습 · 코딩테스트 JS 스택 풀이: 크레인 인형뽑기 처리법
  19. 필수 학습 · 코딩테스트 JS 스택 풀이: 후위식 연산 계산하기
  20. 필수 학습 · 코딩테스트 JS 스택 풀이: 쇠막대기 레이저 절단 개수 세기
  21. 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 두 정렬 배열 합치기
  22. 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기 현재 글
  23. 필수 학습 · 코딩테스트 JS 슬라이딩 윈도우 풀이: 최대 매출 구간 합 계산하기
  24. 필수 학습 · JavaScript 투 포인터: 합이 M인 연속 부분수열 개수
  25. 필수 학습 · 코딩테스트 JS 해시 풀이: 모든 아나그램 찾기
  26. 필수 학습 · 가장 긴 문자열 JavaScript 풀이
  27. 필수 학습 · 가운데 문자 출력 JavaScript 풀이
  28. 필수 학습 · 중복문자제거 JavaScript 풀이
  29. 필수 학습 · 코딩테스트 JS 고급: 최소 힙으로 다익스트라 최단 경로 구하기
  30. 필수 학습 · 코딩테스트 JS Union-Find: 연결 성분 수와 크기 구하기
  31. 필수 학습 · 코딩테스트 JS Trie: 접두사에 맞는 단어 수 세기
  32. 필수 학습 · 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리
  33. 필수 학습 · 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합
  34. 필수 학습 · 코딩테스트 JS LRU 캐시: 지도 타일 재사용 기록
  35. 필수 학습 · 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기
  36. 필수 학습 · 코딩테스트 JS 큐: 상담 창구 대기열 명령 처리
  37. 필수 학습 · 코딩테스트 JS 연결 리스트: 재생 대기 목록 관리
  38. 필수 학습 · JavaScript 원형 덱 연습: 최근 기록 창과 되돌리기
  39. 필수 학습 · JavaScript 해시 테이블 연습: 정규화 문자열 빈도와 등장 순서
  40. 필수 학습 · JavaScript 트리 순회 연습: 깊이별 노드 묶기
  41. 필수 학습 · JavaScript BST 연습: 닫힌 구간의 중복 키 보고서
  42. 필수 학습 · JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러
  43. 필수 학습 · JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기
  44. 필수 학습 · 중복단어제거 JavaScript 풀이
  45. 필수 학습 · TypeScript 이진 탐색 연습: 숫자 카드 존재 여부 확인
  46. 필수 학습 · 큰 수 출력하기 JavaScript 풀이
  47. 필수 학습 · 보이는 학생 JavaScript 풀이
  48. 필수 학습 · 가위바위보 JavaScript 풀이
  49. 필수 학습 · 점수계산 JavaScript 풀이

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기