읽기 전에 알아두면 좋아요
아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.
- 투 포인터의 이동 기준: 양끝 비교와 정렬 배열 병합투 포인터는 두 위치를 움직이며 비교할 범위를 좁히는 방법입니다. 정렬 후 작은 쪽 포인터를 움직이는 교집합 불변식을 확인합니다.
- JavaScript 배열: 인덱스 조회와 삽입·삭제 비용배열은 여러 값을 순서대로 담습니다. 인덱스로 값을 읽고 반복문으로 하나씩 살펴봅니다. 인덱스 접근과 배열을 순회할 때의 연산 비용을 확인합니다.
개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.
과정 위치와 선택 복습 안내
과정 위치: 구간과 탐색 · 문제
과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.
JavaScript로 문법·실행 복습하기 (선택)
개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.
공통 원소 추출 문제에서는 두 정수 배열에서 어떤 원소를 결과 배열에 담나요?
두 정수 배열에 공통으로 있는 원소를 찾아 결과 배열을 반환하는 문제입니다. 배열·정렬·반복문의 기본을 준비하고, 아래 입력·반환 계약을 확인해 직접 풀어보세요.
적용 조건·범위: 실수 포인트: 문제 조건에 “중복 없음”이 없다면 중복 처리 로직을 별도로 고려해야 합니다.
문제
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
직접 풀어보기
먼저 선택한 언어의 입력·반환 계약에 맞춰 작성해 보세요. 힌트 받기와 정답과 비교하기는 코드를 실행하지 않는 AI 분석입니다. 해답·해설은 아래에서 필요할 때 펼칠 수 있습니다.
AI 힌트
코드를 실행한 채점 결과가 아닌 AI의 코드 분석입니다.
해답·해설 보기
function solution(arr1, arr2) {
const a = [... arr1].sort((x, y) => x - y);
const b = [... arr2].sort((x, y) => x - y);
const result = [];
let i = 0, j = 0;
while (i < a.length && j < b.length) {
if (a[i] === b[j]) {
result.push(a[i]);
i++;
j++;
}
else if (a[i] < b[j]) i++;
else j++;
}
return result;
}
solution(arr1, arr2)에 arr1(정수 배열), arr2(정수 배열)를 전달하고 정수 배열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
def solution(arr1, arr2):
a, b = sorted(arr1), sorted(arr2)
result = []
i = j = 0
while i < len(a) and j < len(b):
if a[i] == b[j]:
result.append(a[i])
i += 1
j += 1
elif a[i] < b[j]:
i += 1
else:
j += 1
return result
solution(arr1, arr2)에 arr1(정수 배열), arr2(정수 배열)를 전달하고 정수 배열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
import java.util.*;
class Solution {
public int[] solution(int[] arr1, int[] arr2) {
int[] a = arr1.clone(), b = arr2.clone();
Arrays.sort(a);
Arrays.sort(b);
int[] result = new int[Math.min(a.length, b.length)];
int i = 0, j = 0, count = 0;
while (i < a.length && j < b.length) {
if (a[i] == b[j]) {
result[count++] = a[i];
i++;
j++;
} else if (a[i] < b[j]) i++;
else j++;
}
return Arrays.copyOf(result, count);
}
}
Solution.solution(int[] arr1, int[] arr2)에 arr1(정수 배열), arr2(정수 배열)를 전달하고 정수 배열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <stack>
#include <stdexcept>
std::vector < int > solution(const std::vector < int > & arr1, const std::vector < int > & arr2) {
std::vector < int > a = arr1, b = arr2, result;
std::sort(a.begin(), a.end());
std::sort(b.begin(), b.end());
size_t i = 0, j = 0;
while (i < a.size() && j < b.size()) {
if (a[i] == b[j]) {
result.push_back(a[i]);
i++;
j++;
} else if (a[i] < b[j]) i++;
else j++;
}
return result;
}
solution(arr1, arr2)에 arr1(정수 배열), arr2(정수 배열)를 전달하고 정수 배열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
using System;
using System.Collections.Generic;
public class Solution {
public int[] Solve(int[] arr1, int[] arr2) {
int[] a =(int[]) arr1.Clone(), b =(int[]) arr2.Clone();
Array.Sort(a);
Array.Sort(b);
var result = new List < int >();
int i = 0, j = 0;
while (i < a.Length && j < b.Length) {
if (a[i] == b[j]) {
result.Add(a[i]);
i++;
j++;
} else if (a[i] < b[j]) i++;
else j++;
}
return result.ToArray();
}
}
Solution.Solve(int[] arr1, int[] arr2)에 arr1(정수 배열), arr2(정수 배열)를 전달하고 정수 배열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
내가 푼 방식

한 배열을 순회하면서 다른 배열에 포함되어 있는지 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]));
// 정렬되지 않은 입력
동작 설명: 현재 solution은 내부에서 정렬하므로 이 호출의 실제 결과는 [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) 복사 공간이 추가됩니다. 반대로 현재 코드처럼 제자리 정렬하면 복사 공간은 줄지만 호출 뒤 입력 순서가 달라집니다. 어느 선택이 맞는지는 함수 계약에 포함해야 합니다.
정리: 먼저 입력의 정렬 여부와 중복 정책을 확인하고, 정렬 비용·원본 변경 여부까지 설명할 수 있어야 완성된 풀이입니다. 이 조건을 적은 뒤 포인터가 한 방향으로만 이동한다는 불변식을 제시하면 시간 복잡도 설명도 명확해집니다.
이 문제를 푼 뒤 확인하기
정렬 후 양쪽 포인터가 같은 값에서 만날 때와 한쪽이 먼저 끝날 때를 확인해 보세요.
본문의 예제와 이 반례를 직접 실행해 예상 출력과 비교하고, 결과가 다르면 입력 조건과 반복 범위를 다시 확인하세요.
풀이 함수를 독립 실행해 확인합니다
선수 지식은 배열·조건문·반복문과 위에서 설명한 자료구조입니다. 정답의 완성 함수 블록 하나를 solution.cjs로 저장하고, 그 아래에 다음 호출을 붙입니다. 앞의 비교용 풀이와 같은 이름의 함수를 함께 붙이지 않습니다. 터미널에서 node solution.cjs로 실행합니다.
console.log(JSON.stringify(solution(...[[1, 2], [3, 4]])));
이 변형 호출의 JSON 출력은 []입니다. 기존 함수 블록의 console.log가 있으면 그 출력 다음 줄에서 확인합니다.
이 글이 도움이 되었나요?
이어서 공부하기
체크포인트: 해설을 가리고 다시 풀고, 경계 입력을 하나 바꾸어 예상 결과와 실제 결과를 비교하세요. 포인터를 움직여도 답을 버리지 않는 이유와 양수·0·음수의 차이를 설명하세요.
다음 추천 · 알고리즘 개념: 슬라이딩 윈도우: 고정 길이 합과 양수 배열의 가변 구간
권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.
원래 과정 목록 순서 (선택)
읽음 기록
읽음 — / 전체 58개 · 필수 58개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.