도입
코딩테스트에서 Map은 단순한 자료구조가 아니라 문제 해결 패턴 그 자체로 자주 등장합니다. 특정 값이 몇 번 나왔는지 세거나, 어떤 값을 본 적이 있는지 기록하거나, 값에 해당하는 다른 정보를 즉시 찾는 문제는 대부분 Map으로 매우 효율적으로 풀 수 있습니다.
특히 자바에서는 HashMap, TreeMap, LinkedHashMap을 상황에 맞게 선택하고, getOrDefault, putIfAbsent, computeIfAbsent, merge 같은 메서드를 적절히 활용하면 코드 길이도 줄고 실수도 크게 줄일 수 있습니다.
필요성
배열이나 리스트만으로 문제를 풀면 두 겹 반복문이 필요한 경우가 많습니다. 하지만 Map을 이용하면 “찾는 과정”을 빠르게 바꿀 수 있어서, 시간 복잡도를 크게 줄일 수 있습니다.
- 빈도 수 세기
- 중복 체크
- 특정 값의 첫 위치 / 마지막 위치 저장
- 두 수의 합 / 짝 찾기
- 누적합 빈도 기반 부분합 문제
- 그룹핑
- 정렬된 키 관리가 필요한 문제
구현체 선택
| 구현체 | 언제 쓰나 |
|---|---|
| HashMap | 대부분의 일반적인 문제 |
| TreeMap | 키 정렬이 바로 필요할 때 |
| LinkedHashMap | 입력 순서 유지가 답에 영향을 줄 때 |
자주 쓰는 메서드
map.getOrDefault(key, 0); // 없으면 기본값 사용
map.putIfAbsent(key, value); // 키 없을 때만 넣기
map.computeIfAbsent(key, k -> new ArrayList<>()); // 없으면 생성
map.merge(key, 1, Integer::sum); // 누적 더하기
get()보다 getOrDefault()를 먼저 떠올리는 습관이 좋습니다.패턴 1. 빈도 수 세기
문자열, 숫자, 문자, 이름 등 어떤 값이 몇 번 나왔는지 세는 문제는 거의 항상 Map으로 풀립니다.
Map<String, Integer> freq = new HashMap<>();
for (String word : words) {
freq.put(word, freq.getOrDefault(word, 0) + 1);
}
- 가장 많이 나온 수 찾기
- 문자열에서 각 문자 개수 세기
- 아나그램 판별
- 의상 종류별 개수 세기
패턴 2. 존재 여부 기록
어떤 값이 이미 등장했는지 여부만 중요하면 사실 Set이 더 적합한 경우도 많습니다. 하지만 “등장 여부 + 관련 정보”가 함께 필요하면 Map이 더 강력합니다.
Map<Integer, Boolean> visited = new HashMap<>();
for (int num : nums) {
if (visited.containsKey(num)) {
System.out.println("중복 발견");
}
visited.put(num, true);
}
패턴 3. 위치 기억하기
숫자나 문자열이 처음 나온 위치, 마지막으로 나온 위치, 특정 값의 인덱스를 기억하는 문제는 Map이 매우 잘 맞습니다.
Map<Integer, Integer> firstIndex = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
firstIndex.putIfAbsent(nums[i], i);
}
- 처음 등장한 위치 구하기
- 가장 가까운 같은 문자 찾기
- 중복 원소의 거리 계산
패턴 4. 짝 찾기 / Two Sum
가장 유명한 Map 패턴 중 하나가 바로 Two Sum입니다. 현재 숫자와 짝이 되는 값이 이전에 등장했는지 Map에서 즉시 확인하는 방식입니다.
Map<Integer, Integer> indexMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int targetValue = target - nums[i];
if (indexMap.containsKey(targetValue)) {
return new int[]{indexMap.get(targetValue), i};
}
indexMap.put(nums[i], i);
}
이 패턴은 두 수의 합뿐 아니라, 차이, 보완값, 반대값, 특정 조건을 만족하는 이전 원소 찾기 문제에도 자주 확장됩니다.
패턴 5. 그룹핑
“같은 알파벳으로 시작하는 단어끼리”, “같은 종류끼리”, “같은 해시 결과끼리” 묶는 문제에서는 Map 안에 리스트를 넣는 패턴이 유용합니다.
Map<String, List<String>> group = new HashMap<>();
for (String s : words) {
String key = s.substring(0, 1);
group.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
특히 computeIfAbsent()를 모르면 코드가 장황해지기 쉽기 때문에, 이 메서드는 꼭 익혀두는 것이 좋습니다.
패턴 6. 슬라이딩 윈도우 + 카운팅
길이가 고정되거나 조건을 만족하는 구간을 움직이며 구간 안 원소들의 개수를 관리해야 할 때 Map이 자주 사용됩니다.
Map<Character, Integer> count = new HashMap<>();
int left = 0;
for (int right = 0; right < s.length(); right++) {
char rc = s.charAt(right);
count.put(rc, count.getOrDefault(rc, 0) + 1);
while (count.get(rc) > 1) {
char lc = s.charAt(left++);
count.put(lc, count.get(lc) - 1);
if (count.get(lc) == 0) count.remove(lc);
}
}
- 중복 없는 가장 긴 부분 문자열
- 특정 길이 구간의 문자 빈도 비교
- 아나그램 구간 개수
패턴 7. 누적합 빈도
부분 배열 합, 특정 합을 만드는 구간 개수 문제에서는 현재 누적합과 과거 누적합의 관계를 이용합니다. 이때 과거 누적합 빈도를 Map으로 저장하면 매우 강력합니다.
Map<Integer, Integer> prefixCount = new HashMap<>();
prefixCount.put(0, 1);
int sum = 0;
int answer = 0;
for (int num : nums) {
sum += num;
answer += prefixCount.getOrDefault(sum - k, 0);
prefixCount.put(sum, prefixCount.getOrDefault(sum, 0) + 1);
}
- 부분합이 k인 구간 개수
- 나머지가 같은 누적합 개수
- 누적합 + 해시 응용 문제
패턴 8. 정렬된 키 관리
코딩테스트에서 “가장 작은 키”, “가장 큰 키”, “정렬된 출력”이 문제 조건에 직접 포함되면 HashMap보다 TreeMap이 더 자연스러울 수 있습니다.
TreeMap<Integer, Integer> map = new TreeMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
System.out.println(map.firstKey());
System.out.println(map.lastKey());
패턴 9. 좌표 압축 / 값 매핑
좌표 압축처럼 원래 값을 더 작은 정수 인덱스로 바꿔야 할 때는 정렬한 뒤 “원래 값 → 압축된 값”을 Map에 저장합니다.
int[] sorted = Arrays.stream(nums).distinct().sorted().toArray();
Map<Integer, Integer> rank = new HashMap<>();
for (int i = 0; i < sorted.length; i++) {
rank.put(sorted[i], i);
}
패턴 10. 문자열 문제
- 아나그램 확인 → 문자 빈도 Map
- 가장 많이 등장한 문자 → 카운팅 Map
- 이전 등장 위치 기록 → 문자별 최근 인덱스 Map
- 부분 문자열 빈도 비교 → 슬라이딩 윈도우 + Map
자주 하는 실수
get()결과가 null인데 바로 연산해서 NullPointerException 발생- 카운트 감소 후 0이 된 키를 제거하지 않아 로직 꼬임
- 정렬이 필요한데 HashMap을 쓰고 따로 처리 안 함
- 등장 여부만 필요해도 Map을 남용함 → Set이 더 단순할 수 있음
- 키와 값의 의미를 뒤집어 저장해서 로직이 복잡해짐
풀이 루틴
- 무엇을 기준으로 찾는지 먼저 본다.
- 키는 무엇인지 정한다.
- 값에는 개수 / 인덱스 / 리스트 / 합계 중 무엇이 들어가야 하는지 정한다.
- 정렬이 필요 없으면 우선 HashMap부터 생각한다.
- Null 처리 방지를 위해
getOrDefault나putIfAbsent를 우선 사용한다.
디버깅
요약
- ✅ Map은 코딩테스트에서 O(n²)을 O(n) 수준으로 줄이는 핵심 도구가 될 수 있다.
- ✅ 기본 구현체는 HashMap, 정렬이 필요할 때는 TreeMap을 고려한다.
- ✅ 빈도 수, 위치 기억, 짝 찾기, 그룹핑, 누적합 문제에서 매우 자주 등장한다.
- ✅ getOrDefault, putIfAbsent, computeIfAbsent, merge를 익혀두면 코드가 짧고 안전해진다.
- ✅ 문제를 보면 “키에 무엇을 넣고, 값에 무엇을 넣을지”부터 정의하는 습관이 중요하다.