🃏 [백준 27160] 할리갈리: HashMap과 배열, ENUM
🌟 Today's Focus
- HashMap 연산과 활용
- 자료구조 성능 최적화
- Java Stream API
- 입출력 처리 최적화
📝 Today I Learned
1. 문제 분석 (재정의)
오늘 도전한 문제는 할리갈리라는 카드 게임을 구현하는 것이다. 테이블에 여러 장의 카드가 있고, 각 카드에는 과일의 종류와 개수가 적혀있다. 같은 종류의 과일이 정확히 5개가 되는 순간을 찾아내야 한다. 처음 문제를 보았을 때는 배열로 간단하게 해결할 수 있겠다고 생각했다.
하지만 오늘 Hash 특강을 듣고 나서, HashMap을 활용하면 더 우아한 해결책이 될 수 있겠다는 생각이 들었다.
2. 문제 접근 방식
처음에는 단순히 이렇게 접근했다:
int[] fruitCounts = new int[4]; // 과일 종류가 4개로 고정
// STRAWBERRY = 0, BANANA = 1, LIME = 2, PLUM = 3
그러나 Hash 특강에서 배운 내용을 적용하면서 생각이 바뀌었다:
Map<String, Integer> fruitCount = new HashMap<>();
// 과일 이름을 직접 키로 사용할 수 있다!
3. 문제 해결 과정
첫 번째 시도: 기본 배열 접근
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
int[] fruitCount = new int[4];
for (int i = 0; i < N; i++) {
String[] input = br.readLine().split(" ");
int index = getFruitIndex(input[0]); // 별도의 매핑 함수 필요
fruitCount[index] += Integer.parseInt(input[1]);
}
boolean found = false;
for (int count : fruitCount) {
if (count == 5) {
found = true;
break;
}
}
System.out.println(found ? "YES" : "NO");
}
private static int getFruitIndex(String fruit) {
switch (fruit) {
case "STRAWBERRY": return 0;
case "BANANA": return 1;
case "LIME": return 2;
default: return 3; // PLUM
}
}
}
이 방식의 문제점:
- 과일 이름과 인덱스 매핑이 필요하다
- 코드가 직관적이지 않다
- 새로운 과일이 추가되면 코드 수정이 필요하다
두 번째 시도: HashMap 활용
Hash 특강을 듣고 나서 시도한 개선된 버전이다.
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
Map<String, Integer> fruitCount = new HashMap<>();
while (N-- > 0) {
StringTokenizer st = new StringTokenizer(br.readLine());
String fruit = st.nextToken();
int count = Integer.parseInt(st.nextToken());
// merge 메서드 활용
fruitCount.merge(fruit, count, Integer::sum);
}
System.out.println(fruitCount.values().stream()
.anyMatch(count -> count == 5) ? "YES" : "NO");
}
}
세 번째 시도: ENUM을 활용한 타입 안전성 확보
ENUM을 사용하면 컴파일 타임에 타입 안전성을 확보할 수 있다는 점을 떠올렸다. 실수로 잘못된 문자열을 입력하는 것을 방지할 수 있기 때문이다.
import java.io.*;
public class Main {
enum Fruit {
STRAWBERRY, BANANA, LIME, PLUM;
// 문자열을 ENUM으로 안전하게 변환
public static Fruit from(String name) {
try {
return Fruit.valueOf(name);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("유효하지 않은 과일 이름입니다: " + name);
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
// 각 ENUM 값에 대한 카운트를 저장할 배열
int[] counts = new int[Fruit.values().length];
while (N-- > 0) {
StringTokenizer st = new StringTokenizer(br.readLine());
Fruit fruit = Fruit.from(st.nextToken());
int count = Integer.parseInt(st.nextToken());
counts[fruit.ordinal()] += count;
}
boolean hasExactlyFive = false;
for (int count : counts) {
if (count == 5) {
hasExactlyFive = true;
break;
}
}
System.out.println(hasExactlyFive ? "YES" : "NO");
}
}
ENUM을 사용한 접근의 장점:
- 컴파일 타임에 타입 안전성 확보
- IDE의 자동 완성 지원
- 과일 종류가 제한적이므로 메모리 사용 최적화
- 잘못된 입력에 대한 명확한 예외 처리 가능
단점:
- ENUM 정의로 인한 추가 코드 작성 필요
- valueOf() 메서드 호출 시 약간의 오버헤드 발생
- 새로운 과일 추가 시 ENUM 수정 필요
4. 최종 개선 사항
StringTokenizer
사용으로 문자열 파싱 성능 개선merge
메서드로 코드 간결화- Stream API로 가독성 향상
💭 Reflection
어려웠던 점과 해결 방법
처음에는 가장 기본적인 배열로 접근했는데, 이는 과일 이름과 인덱스 간의 매핑이 필요해서 코드가 복잡해졌다. Hash 특강을 통해 HashMap의 장점을 알게 되었고, 이를 적용하여 더 깔끔한 코드를 작성할 수 있었다.
세 가지 방식의 성능 비교
각 방식별로 백준에서 테스트한 결과는 다음과 같다.
구현 방식 | 실행 시간 | 메모리 사용량 | 코드 라인 수 |
배열 | 128ms | 14,196KB | 35줄 |
HashMap | 136ms | 14,320KB | 28줄 |
ENUM | 124ms | 14,244KB | 45줄 |
ENUM 방식이 약간의 코드량 증가에도 불구하고 실행 시간과 메모리 사용량 측면에서 좋은 성능을 보여주었다. 이는 ENUM의 ordinal()을 통한 배열 접근이 HashMap의 해시 연산보다 효율적이기 때문이다.
새롭게 알게된 점
- HashMap의
merge
메서드가 이런 상황에서 매우 유용하다 - 입력 처리할 때
StringTokenizer
가split
보다 효율적일 수 있다 - 때로는 가장 단순해 보이는 방법이 최선이 아닐 수 있다
- ENUM의 ordinal()과 valueOf() 메서드의 활용
- 타입 안전성과 성능 사이의 트레이드오프
앞으로 학습할 것
- HashMap의 다른 유용한 메서드들 살펴보기
- 입출력 처리 최적화 방법 더 알아보기
- Stream API의 다양한 활용법 학습하기
- ENUM의 다양한 활용 방법 학습하기
- 자바의 타입 시스템 깊이 있게 이해하기
이렇게 세 가지 서로 다른 접근 방식을 통해 같은 문제를 해결해보니, 각각의 장단점을 더 명확하게 이해할 수 있었다. 특히 ENUM을 활용한 방식은 타입 안전성이라는 새로운 관점을 제시해 주었다.
'DSA > 코딩테스트' 카테고리의 다른 글
Java 코딩테스트 완벽 가이드 - Part 1: 입출력 최적화 (0) | 2025.01.22 |
---|---|
99클럽 코테 스터디 7일차 TIL - Java 백준 15829 해싱 (1) | 2025.01.22 |
99클럽 코테 스터디 5일차 TIL - Java 백준 P10798 세로 읽기 (0) | 2025.01.17 |
99클럽 코테 스터디 4일차 TIL - Java 백준 P2343 기타 레슨 이진 탐색으로 최적의 값 찾기(파라메트릭 서치) (0) | 2025.01.17 |
99클럽 코테 스터디 3일차 TIL - Java 백준 P2675 문자열 반복 (1) | 2025.01.15 |