주요 포인트 한눈에 보기
후보 기호의 득표수를 세면서 조건문 묶음과 Map 중 무엇이 확장에 유리한지 배우는 JavaScript 빈도 집계 문제입니다. 키 삽입 순서가 동률 처리에 미치는 영향, 빈 입력과 유일한 승자 가정, 명세가 보장하는 성능 범위를 함께 확인합니다.
문제 설명

학급 회장을 뽑기 위해 후보 기호 A, B, C, D, E가 등록되어 있습니다.
투표용지에는 반 학생들이 자기가 선택한 후보의 기호(알파벳)를 하나씩 작성하며,
선생님은 이 기호들을 순서대로 발표합니다.
선생님의 발표가 모두 끝난 후,
어떤 기호의 후보가 학급 회장이 되었는지를 출력하는 프로그램을 작성하세요.
반드시 한 명의 학급 회장만 선출되는 투표 결과만 주어진다고 가정합니다.
입력 설명
첫 번째 줄에는 반 학생 수 N이 주어집니다. (5 ≤ N ≤ 50)
두 번째 줄에는 N개의 투표용지에 쓰여 있던 각 후보의 기호가
선생님이 발표한 순서대로 문자열로 주어집니다.
출력 설명
학급 회장으로 선택된 후보의 기호를 출력합니다.
입력 예제
15
BACBACCACCBDEDE
출력 예제
C
내가 푼 풀이
function solution(s) {
let arr = s.split("");
let obj = arr.reduce((acc, cur) => {
acc[cur] += 1;
return acc;
}, { A: 0, B: 0, C: 0, D: 0, E: 0 });
let maxKey = null;
let maxValue = -Infinity;
for (const key in obj) {
if (obj[key] > maxValue) {
maxValue = obj[key];
maxKey = key;
}
}
return maxKey;
}
이 풀이는 후보가 A~E로 고정되어 있다는 문제 조건을 코드에 직접 반영한 방식입니다. 객체를 미리 초기화해 두고, 문자열을 순회하며 각 후보의 득표 수를 누적합니다.
동작 메커니즘은 단순합니다. 문자열을 배열로 변환한 뒤, reduce를 이용해 각 문자에 해당하는 카운트를 증가시키고, 이후 한 번의 반복으로 최댓값을 가진 키를 찾습니다.
실수하기 쉬운 포인트는 초기 객체에 없는 문자가 등장할 가능성을 고려하지 않았다는 점입니다. 현재 문제에서는 조건상 문제가 없지만, 입력 조건이 조금만 바뀌면 런타임 오류 또는 잘못된 결과로 이어질 수 있습니다.
개선 방향으로는 초기 객체를 하드코딩하지 않고, 등장하는 값만 동적으로 집계하도록 구조를 바꾸는 것이 좋습니다.
정답 풀이

function solution(s) {
let answer;
let sH = new Map();
for (let x of s) {
if (sH.has(x)) sH.set(x, sH.get(x) + 1);
else sH.set(x, 1);
}
let max = Number.MIN_SAFE_INTEGER;
for (let [key, val] of sH) {
if (val > max) {
max = val;
answer = key;
}
}
return answer;
}
정답 풀이는 후보의 종류를 사전에 가정하지 않습니다. 문자열을 순회하면서 실제로 등장한 문자만 Map에 저장하고, 그 빈도를 누적합니다.
이 방식의 핵심은 확장성입니다. 후보가 늘어나거나 문자 종류가 변경되더라도 코드 수정 없이 그대로 동작합니다.
일반적인 JavaScript 구현에서는 각 Map 접근을 평균 상수 시간으로 보고 전체를 O(N)으로 설명합니다. 다만 명세가 특정 해시 테이블 구현이나 모든 접근의 정확한 O(1)을 보장하는 것은 아닙니다.
출제 의도
이 문제는 단순 구현 문제가 아니라, 문자열 데이터를 집계할 때 어떤 자료구조를 선택해야 하는지를 묻는 문제입니다.
출제자는 반복문과 조건문보다, 해시(Map)를 이용한 빈도 집계 패턴을 자연스럽게 떠올리는지를 확인하고자 합니다.
문제의 입력 조건과 시간·메모리 제한을 만족한다면 초기화된 객체를 쓰는 풀이도 정답입니다. 여기서는 다른 문제로 확장하기 쉬운 빈도 집계 패턴을 익히기 위해 Map 풀이를 함께 제시합니다.
이 문제는 이후 등장하는 해시 문제들의 기초 사고를 형성하기 위한 전형적인 입문 문제로 볼 수 있습니다.
빈도 비교 문제를 한 단계 확장하려면 JS 아나그램 슬라이딩 윈도우를 함께 읽으면 흐름이 연결됩니다.
Map 동작·동률·검증 기준
Map은 키와 값을 저장하고 키가 처음 삽입된 순서를 기억합니다. 따라서 현재 정답 코드는 동률이면 투표 문자열에서 먼저 처음 등장해 Map에 삽입된 후보를 반환합니다. 문제가 유일한 최다 득표자를 보장하지 않는다면 사전순, 공동 우승자 배열, 재투표 같은 동률 규칙을 별도로 정해야 합니다.
JavaScript 명세는 Map 접근 시간이 원소 수에 대해 평균적으로 선형보다 빨라야 한다고 요구하지만, 내부 구현이 반드시 해시 테이블이거나 모든 연산이 정확히 O(1)이라고 보장하지는 않습니다. 코딩 테스트에서는 일반적인 구현을 전제로 전체 순회를 O(N)으로 설명하되, 이 구분은 MDN Map 문서와 함께 적는 편이 정확합니다.
console.assert(solution('BACBACCACCBDEDE') === 'C');
console.assert(solution('AB') === 'A'); // 현재 구현의 동률 규칙: 먼저 삽입된 후보
console.assert(solution('') === undefined); // 빈 입력 정책을 호출부에서 결정
빈 문자열에서는 answer가 정해지지 않아 undefined를 반환합니다. 문제 입력이 최소 한 표를 보장하면 괜찮지만, 범용 함수라면 빈 입력에서 오류를 낼지, null을 반환할지 명시하세요.
A~E만 나온다는 현재 조건에서는 초기값을 가진 일반 객체 풀이도 정확합니다. 후보가 동적으로 늘어나면 Map은 별도 초기화 없이 키를 추가할 수 있고, 문자열뿐 아니라 다른 타입도 키로 사용할 수 있다는 장점이 있습니다. 객체로 일반화한다면 상속된 프로퍼티와 사용자 입력 키가 충돌하지 않도록 Object.create(null) 또는 소유 프로퍼티 검사도 고려해야 합니다.
입력 검증 역시 자료구조 선택과 별개입니다. 허용되지 않은 기호를 무시할지 오류로 처리할지, 대소문자를 같은 후보로 볼지, 공백을 투표로 셀지를 정하지 않으면 같은 함수가 호출 위치마다 다른 의미로 사용될 수 있습니다.
정리: 빈도 집계는 ‘키가 늘어나도 코드가 바뀌지 않는가’뿐 아니라 빈 입력과 동률을 어떻게 처리하는지까지 정해야 완성됩니다. 이 세 조건을 테스트에 고정하면 후보 종류가 바뀌어도 안전하게 재사용할 수 있습니다. 특히 동률 규칙은 구현의 우연한 순서에 맡기지 말고 요구사항 이름으로 테스트에 남기세요.
QnA
Q. 내가 푼 풀이는 틀린 답인가요?
아닙니다. 현재 문제 조건에서는 완전히 올바른 답입니다. 다만 조건이 바뀌면 바로 수정이 필요하다는 한계가 있습니다.
Q. 왜 Map 풀이가 정답으로 제시되나요?
후보의 종류가 달라지거나 늘어나더라도 그대로 사용할 수 있는 범용적인 해시 패턴이기 때문입니다.
Q. 실전 코딩테스트에서는 어떤 풀이가 안전한가요?
조건 변화에 강한 Map 기반 풀이가 가장 안전하며, 출제 의도에도 가장 잘 부합합니다.
같이 읽으면 좋은 글
구현 원리로 돌아가기
풀이에 사용한 자료구조를 왜 선택하는지, 코드의 동작과 연산 비용을 함께 복습하세요.
풀이 전에 확인할 순서
- 입력값과 출력값을 한 문장으로 다시 적습니다.
- 반복할 대상과 비교·저장할 값을 정합니다.
- 필요한 자료구조와 시간복잡도를 예상합니다.
- 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기
정답 코드를 바로 따라 쓰기보다, 본문에서 값이 갱신되는 조건과 반복 범위를 먼저 찾으세요. 반복 한 번마다 반드시 유지되어야 하는 값이 무엇인지 적으면 풀이의 중심 변수를 고르기 쉽습니다.
테스트 확인
- 가능한 가장 작은 입력
- 같은 값이나 문자가 반복되는 입력
- 정답이 처음 또는 마지막 위치에서 결정되는 입력
- 입력 제한에 가까운 경우의 실행 시간
확인 결과: 본문의 예제뿐 아니라 위 경계 사례에서도 예상값과 실제 출력이 같아야 풀이가 완료됩니다.
이 글이 도움이 되었나요?
코딩테스트 JavaScript 학습 순서
필수 49개 · 전체 49개
읽음 기록 관리
전체 과정 목차 (49개)
- 필수 길잡이 · 코딩테스트 JS 자료구조 로드맵: 배열, 해시, 스택, 투 포인터 순서
- 필수 학습 · 세 수 중 최솟값 JavaScript 조건문 풀이 정리
- 필수 학습 · 삼각형 판별하기 JavaScript 풀이
- 필수 학습 · 연필 개수 JavaScript 풀이
- 필수 학습 · 1부터 N까지 합 출력하기 JavaScript 풀이
- 필수 학습 · 최솟값 구하기 JavaScript 풀이|배열 순회와 비교 갱신 원리
- 필수 학습 · 홀수 JavaScript 풀이: 조건 판별과 결과 처리 정리
- 필수 학습 · 10부제 JavaScript 풀이: 끝자리 비교로 위반 차량 수 세기
- 필수 학습 · A를 #으로 JavaScript 풀이: 문자열 순회와 치환
- 필수 학습 · 문자 찾기 JavaScript 풀이: 문자열 순회로 개수 세기
- 필수 학습 · 대문자 찾기 JavaScript 풀이
- 필수 학습 · 대문자로 통일 JavaScript 풀이
- 필수 학습 · 대소문자 변환 JavaScript 풀이
- 필수 학습 · 일곱 난쟁이 JavaScript 풀이: 두 명을 제외하는 완전탐색
- 필수 학습 · 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기 현재 글
- 필수 학습 · 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 괄호문자 제거하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 크레인 인형뽑기 처리법
- 필수 학습 · 코딩테스트 JS 스택 풀이: 후위식 연산 계산하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 쇠막대기 레이저 절단 개수 세기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 두 정렬 배열 합치기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기
- 필수 학습 · 코딩테스트 JS 슬라이딩 윈도우 풀이: 최대 매출 구간 합 계산하기
- 필수 학습 · JavaScript 투 포인터: 합이 M인 연속 부분수열 개수
- 필수 학습 · 코딩테스트 JS 해시 풀이: 모든 아나그램 찾기
- 필수 학습 · 가장 긴 문자열 JavaScript 풀이
- 필수 학습 · 가운데 문자 출력 JavaScript 풀이
- 필수 학습 · 중복문자제거 JavaScript 풀이
- 필수 학습 · 코딩테스트 JS 고급: 최소 힙으로 다익스트라 최단 경로 구하기
- 필수 학습 · 코딩테스트 JS Union-Find: 연결 성분 수와 크기 구하기
- 필수 학습 · 코딩테스트 JS Trie: 접두사에 맞는 단어 수 세기
- 필수 학습 · 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리
- 필수 학습 · 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합
- 필수 학습 · 코딩테스트 JS LRU 캐시: 지도 타일 재사용 기록
- 필수 학습 · 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기
- 필수 학습 · 코딩테스트 JS 큐: 상담 창구 대기열 명령 처리
- 필수 학습 · 코딩테스트 JS 연결 리스트: 재생 대기 목록 관리
- 필수 학습 · JavaScript 원형 덱 연습: 최근 기록 창과 되돌리기
- 필수 학습 · JavaScript 해시 테이블 연습: 정규화 문자열 빈도와 등장 순서
- 필수 학습 · JavaScript 트리 순회 연습: 깊이별 노드 묶기
- 필수 학습 · JavaScript BST 연습: 닫힌 구간의 중복 키 보고서
- 필수 학습 · JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러
- 필수 학습 · JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기
- 필수 학습 · 중복단어제거 JavaScript 풀이
- 필수 학습 · TypeScript 이진 탐색 연습: 숫자 카드 존재 여부 확인
- 필수 학습 · 큰 수 출력하기 JavaScript 풀이
- 필수 학습 · 보이는 학생 JavaScript 풀이
- 필수 학습 · 가위바위보 JavaScript 풀이
- 필수 학습 · 점수계산 JavaScript 풀이
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.