Algorithm

[알고리즘] 성격 유형 검사하기 (2022 KAKAO TECH INTERNSHIP)

montmer27 2026. 4. 29. 11:05

Github 링크

https://github.com/ginsengcandy/Coding-Test-Practice/commit/7fed26a8520b92e82ea6ad850445725ea58ed3c6

 

[level 1] Title: 성격 유형 검사하기, Time: 1.45 ms, Memory: 91.8 MB -BaekjoonHub · ginsengcandy/Coding-Test-Practice@7f

 

github.com

문제 출처

https://school.programmers.co.kr/learn/courses/30/lessons/118666

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

나의 코드

import java.util.*;

class Solution {
    public String solution(String[] survey, int[] choices) {
        // Map에 포함된 값들이 점수를 얻으면 더하고, 대응하는 값들이 점수를 얻으면 차감한다. 총합이 0 이상이면 Map에 포함된 값으로, 음수이면 대응하는 값을 선택하여 문자열을 생성한다.
        Map<Character, Integer> priorityMap = new LinkedHashMap<>();
        priorityMap.put('R', 0);
        priorityMap.put('C', 0);
        priorityMap.put('J', 0);
        priorityMap.put('A', 0);
        for(int i = 0; i < survey.length; i++) {
            // 첫번째 문자 확인
            char firstChar = survey[i].charAt(0);
            
            switch(firstChar) {
                case 'R':
                    priorityMap.put('R', priorityMap.getOrDefault('R', 0) - (choices[i]-4));
                    break;
                case 'T':
                    priorityMap.put('R', priorityMap.getOrDefault('R', 0) + (choices[i]-4));
                    break;
                case 'C':
                    priorityMap.put('C', priorityMap.getOrDefault('C', 0) - (choices[i]-4));
                    break;
                case 'F':
                    priorityMap.put('C', priorityMap.getOrDefault('C', 0) + (choices[i]-4));
                    break;
                case 'J':
                    priorityMap.put('J', priorityMap.getOrDefault('J', 0) - (choices[i]-4));
                    break;
                case 'M':
                    priorityMap.put('J', priorityMap.getOrDefault('J', 0) + (choices[i]-4));
                    break;
                case 'A':
                    priorityMap.put('A', priorityMap.getOrDefault('A', 0) - (choices[i]-4));
                    break;
                case 'N':
                    priorityMap.put('A', priorityMap.getOrDefault('A', 0) + (choices[i]-4));
                    break;
            }
        }
        
        StringBuilder sb = new StringBuilder();
        for(Character key : priorityMap.keySet()) {
            if(priorityMap.get(key) < 0) {
                switch(key) {
                    case 'R':
                        sb.append('T');
                        break;
                    case 'C':
                        sb.append('F');
                        break;
                    case 'J':
                        sb.append('M');
                        break;
                    case 'A':
                        sb.append('N');
                        break;
                }
            }
            else {
                sb.append(key);
            }
        }
        return sb.toString();
    }
}

오늘 배운 것

Map.of()로 만든 Map은 불변이고(Immutable), 순서도 보장되지 않는다. 따라서 put()을 사용할 수 없다.
HashMap은 순서를 보장하지 않는다. 순서를 보장하기 위해선 LinkedHashMap을 사용해야 한다.
수정 가능한 Map은 new HashMap<>()으로 만들어야 한다. 비어 있는 상태에서 만들고 put()을 통해 원소를 넣거나, Map.of()으로 초기화한 불변 맵을 new HashMap<>()으로 감싸면 된다.
Map의 모든 키를 추출하는 방법은 keySet()이다. getKeys()가 아니다. 참고로 배열이 아니라 Set 형태다.

옵션 1

Map<Character, Integer> priorityMap = new HashMap<>();
priorityMap.put('R', 0);
priorityMap.put('C', 0);
priorityMap.put('J', 0);
priorityMap.put('A', 0);

옵션 2

Map<Character, Integer> priorityMap = new HashMap<>(Map.of('R', 0, 'C', 0, 'J', 0, 'A', 0));

 

HashMap은 순서를 보장하지 않기 때문에, 순서를 보장하려면 LinkedHashMap을 사용한다.

Before

- 코드

Map<Character, Integer> priorityMap = new HashMap<>(Map.of('R', 0, 'C', 0, 'J', 0, 'A', 0));

- 결과

After (LinkedHashMap으로 전환 후)

- 코드

Map<Character, Integer> priorityMap = new LinkedHashMap<>(Map.of('R', 0, 'C', 0, 'J', 0, 'A', 0));

- 결과

- 실패 원인 추정

Map.of()에서 순서가 바뀌는 것 같다.

After(LinkedHashMap 생성 후 하나씩 put)

- 코드

Map<Character, Integer> priorityMap = new LinkedHashMap<>();
        priorityMap.put('R', 0);
        priorityMap.put('C', 0);
        priorityMap.put('J', 0);
        priorityMap.put('A', 0);

- 결과

- 전체 실행 결과: 통과

답안 효율성 평가

메모리: 91.8 MB, 시간: 1.45 ms

전체 평가: ⭐⭐⭐⭐ (우수)

정답률 100/100을 받은 코드입니다. 시간 복잡도와 접근 방식 모두 이 문제에 적합합니다. 구체적으로 분석하겠습니다.


시간 복잡도 및 공간 복잡도

  • 시간 복잡도: O(n) — survey 배열을 한 번 순회하고, 이후 4개의 고정 키를 순회하므로 사실상 O(n)입니다. 이 문제에서 가능한 최선의 복잡도입니다.
  • 공간 복잡도: O(1) — 고정 크기(4개 항목)의 Map과 4글자 StringBuilder만 사용합니다.

잘된 점

핵심 아이디어가 영리합니다. choices[i] - 4를 이용해 점수의 부호만으로 비동의/동의 방향을 한 번에 처리한 점이 핵심입니다. 예를 들어 choices가 1이면 -3, 7이면 +3이 되어 별도의 분기 없이 점수를 누적할 수 있습니다.

LinkedHashMap으로 삽입 순서를 보장한 것도 올바른 선택입니다. 이후 결과 문자열 생성 시 지표 순서(R→C→J→A)를 유지해야 하므로 필요한 처리입니다.

StringBuilder 사용으로 문자열 연결의 불필요한 객체 생성을 피한 것도 좋습니다.


개선 가능한 부분

switch문이 중복·장황합니다. 현재 첫 번째 글자를 기준으로 8개 case를 모두 나열하지만, survey[i]의 두 글자만 보면 아래처럼 훨씬 간결하게 처리할 수 있습니다.

char first = survey[i].charAt(0);
char second = survey[i].charAt(1);
int score = choices[i] - 4;

// choices < 4면 first 유형 점수, > 4면 second 유형 점수
if (score < 0) {
    priorityMap.merge(first, -score, Integer::sum);
} else {
    priorityMap.merge(second, score, Integer::sum);
}

이렇게 하면 8개의 case가 4줄로 줄고, 새로운 지표 유형이 추가되어도 코드 수정이 필요 없습니다.

getOrDefault가 불필요합니다. 이미 맵에 초기값 0을 세팅했기 때문에 getOrDefault의 기본값 부분은 실제로 사용되지 않습니다. priorityMap.get(key) 또는 merge로 대체하는 것이 더 명확합니다.

결과 생성 시 두 번째 switch문도 Map으로 대체 가능합니다.

Map<Character, Character> pairMap = Map.of('R','T','C','F','J','M','A','N');
for (char key : priorityMap.keySet()) {
    sb.append(priorityMap.get(key) < 0 ? pairMap.get(key) : key);
}

정리

항목 평가

시간 복잡도 O(n) — 최적
공간 복잡도 O(1) — 최적
핵심 로직 choices - 4 트릭으로 깔끔하게 처리
코드 간결성 switch 중복이 있어 개선 여지 있음
가독성 주석이 있어 의도 파악 용이

알고리즘 자체는 완벽하게 최적화되어 있으며, 개선 포인트는 성능보다는 코드 중복 제거와 유지보수성 측면에 해당합니다.

다른 우수 답안과의 비교 분석


핵심 접근 방식 비교

**내 답안 (김*현)**은 choices[i] - 4의 부호만으로 비동의/동의 방향을 한 번에 처리하는 방식입니다. 4개의 우선 키(R/C/J/A)만 Map에 저장하고, 음수면 대응 유형을 선택합니다.

다른 답안들의 접근 방식을 정리하면 아래와 같습니다.

풀이 핵심 방식 코드 길이 특징

내 답안 4키 Map + choices-4 부호 중간 switch 8 case 중복
altdmfk 외 (좋아요 38) 8키 Map + score[] 배열 + char[][] 쌍 짧음 가장 간결하고 직관적
450276 외 (좋아요 16) 8키 Map + choices-4 절댓값 직접 비교 짧음 명확한 분기 처리
최석현 OOP + Stream + Enum 풀 설계 매우 김 설계 완성도 극단적으로 높음
Elmo 외 22명 8키 int배열 + HashMap index 중간 배열 기반, 가장 low-level

가장 주목할 만한 답안: altdmfk (좋아요 38위)

char [][] type = {{'R', 'T'}, {'C', 'F'}, {'J', 'M'}, {'A', 'N'}};
int [] score = {0, 3, 2, 1, 0, 1, 2, 3};
// choices[idx] > 4면 두번째 유형에, 아니면 첫번째 유형에 score 누적

이 코드의 장점은 score[] 배열 인덱싱으로 choices 값을 점수로 즉시 변환한다는 점입니다. 별도의 계산 없이 score[choices[idx]]만으로 점수가 나와 가독성이 매우 높습니다. 결과 생성 시에도 char[][] 배열의 순서가 이미 사전순으로 정렬되어 있어 <= 하나로 동점 처리까지 자연스럽게 해결됩니다.

내 답안과의 핵심 차이는 "Map 키 개수"입니다. 내 답안은 4개 키만 두고 부호로 방향을 판단했지만, 상위 답안들은 8개 키를 전부 두고 각 유형을 독립적으로 누적한 뒤 마지막에 쌍으로 비교합니다. 후자가 더 직관적이고, 결과 생성 로직도 단순해집니다.


내 답안의 상대적 위치

알고리즘 아이디어 면에서는 choices - 4 부호 트릭을 쓴다는 점이 좋아요 16위 답안(450276)과 완전히 동일한 발상입니다. 다만 내 답안은 4키 Map을 쓰면서 switch 8 case를 작성한 반면, 450276 답안은 8키 Map을 쓰면서 survey[i].charAt(0/1)을 직접 키로 사용해 switch 없이 처리했습니다. 결과적으로 같은 O(n) 복잡도이지만 코드 간결함에서 차이가 납니다.

좋아요 38위 답안은 score 배열 하나로 계산을 완전히 테이블화한 것이 영리하며, 내 답안보다 약 10줄 짧고 switch가 전혀 없습니다.


결론

내 답안은 시간/공간 복잡도 면에서 다른 상위 풀이들과 동급입니다. 개선 방향을 한 가지만 꼽자면, 8키 Map을 사용해 survey[i].charAt(0/1)을 키로 직접 쓰는 방식으로 switch를 제거하는 것입니다. 이렇게 하면 altdmfk, 450276 풀이처럼 훨씬 짧고 유지보수하기 쉬운 코드가 됩니다.

 

See Also:

1. Markdown to Text Converter

https://markdowntotext.com/

 

Markdown to Text Converter - Free Online Tool

Convert your Markdown to plain text, rich text, or HTML instantly. Perfect for cleaning up AI-generated content.

markdowntotext.com

2. [JAVA] Map이란?

https://devlogofchris.tistory.com/41

 

[JAVA]Map이란? (HashMap, Hashtable, TreeMap)

Map 컬렉션 클래스 Map 인터페이스는 Collection 인터페이스와는 다른 저장 방식을 가집니다. Map 인터페이스를 구현한 Map 컬렉션 클래스들은 키와 값을 하나의 쌍으로 저장하는 방식(key-value 방식)을

devlogofchris.tistory.com

 

'Algorithm' 카테고리의 다른 글

[알고리즘] 기사단원의 무기  (0) 2026.05.16
[알고리즘] 체육복  (0) 2026.04.28
[알고리즘] 둘만의 암호  (0) 2026.04.24
[알고리즘] 대충 만든 자판  (0) 2026.04.22
[알고리즘] 숫자 짝꿍  (0) 2026.04.08