코테/lvl2

프로그래머스 후보키(lvl2)풀어보기

디비드킴 2026. 9. 15. 13:35

문제링크

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

 

프로그래머스

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

programmers.co.kr


문제 핵심

  • 관계 데이터베이스에서 릴레이션(Relation)의 튜플(Tuple)을 유일하게 식별할 수 있는 속성(Attribute) 또는 속성의 집합 중, 다음 두 성질을 만족하는 것을 후보 키(Candidate Key)라고 한다.
    • 유일성(uniqueness) : 릴레이션에 있는 모든 튜플에 대해 유일하게 식별되어야 한다.
    • 최소성(minimality) : 유일성을 가진 키를 구성하는 속성(Attribute) 중 하나라도 제외하는 경우 유일성이 깨지는 것을 의미한다. 즉, 릴레이션의 모든 튜플을 유일하게 식별하는 데 꼭 필요한 속성들로만 구성되어야 한다.

문제 풀이법

-배열의 모든조합키 만들기

문제에서 제시한 

유일성 검사하기

최소성 검사하기


코드

 public static int solution(String[][] relation) {
        int answer = 0;
        Map<String, List<String>> map = new LinkedHashMap<>();
        for (int i = 0; i < relation.length; i++) {
            boolean[] visited = new boolean[relation[0].length];
            for (int ii = 0; ii < relation[0].length; ii++) {
                System.out.println(relation[i][ii] + "," + ii + "");
                List<String> list = new LinkedList<>();
                if (map.containsKey(ii + "")) {
                    list = map.get(ii + "");
                }
                list.add(relation[i][ii]);
                map.put(ii + "", list);
                visited[ii] = true;
                dfs(visited, relation[i], relation[i][ii], ii + 1, ii + "", map);
                visited[ii] = false;
            }
        }
        System.out.println("map: " + map);
        List<String> list = new ArrayList<>();
        for (Map.Entry<String, List<String>> entry : map.entrySet()) {
            String key = entry.getKey();
            List<String> value = entry.getValue();
            if (new HashSet<>(value).size() != value.size()) {
                System.out.println(key + "중복 있음");
            } else {
                System.out.println(key + "중복 없음");
                list.add(key);
            }
        }
        System.out.println(list);
        for (int i = 0; i < list.size(); i++) {
            boolean flag = true;
            for (int ii = 0; ii < list.size(); ii++) {
                if (i == ii) {
                    continue;
                }
                String fk = list.get(i);
                String sk = list.get(ii);
                Set<Character> fkSet = fk.chars()
                        .mapToObj(c -> (char) c)
                        .collect(Collectors.toSet());

                Set<Character> skSet = sk.chars()
                        .mapToObj(c -> (char) c)
                        .collect(Collectors.toSet());
                System.out.println("fkSet: " + fkSet);
                System.out.println("skSet: " + skSet);
                if (skSet.containsAll(fkSet) || fkSet.containsAll(skSet)) {
                    if (fkSet.size() > skSet.size()) {
                        System.out.println("fk가 sk의 부분집합");
                        flag = false;
                        break;
                    }
                }
            }
            if (flag) {
                System.out.println("포함안됨");
                answer += 1;
            }
        }
        System.out.println(answer);
        return answer;
    }

    public static void dfs(boolean[] visited, String[] relation, String sout, int start, String key,
            Map<String, List<String>> map) {
        for (int ii = start; ii < relation.length; ii++) {
            if (!visited[ii]) {
                visited[ii] = true;
                System.out.println(sout + relation[ii] + "," + key + ii);
                List<String> list = new LinkedList<>();
                if (map.containsKey(key + ii)) {
                    list = map.get(key + ii);
                }
                list.add(sout + relation[ii]);
                map.put(key + ii, list);
                // System.out.println("자식 시작: " + ii);
                dfs(visited, relation, sout + relation[ii], ii + 1, key + ii, map);
                visited[ii] = false;
                // System.out.println("백트래킹: " + ii + Arrays.toString(visited));
            }

        }
    }

 


결과



gpt피드백

-핵심 개선점만 요약하면:

  1. DFS를 행마다 돌리지 않기
    • 현재: 각 행마다 같은 컬럼 조합을 반복 생성
    • 개선: 컬럼 조합은 DFS로 한 번만 생성 → 그 조합으로 모든 행 검사
  2. visited 생략 가능
    • 조합 DFS는 start = ii + 1 때문에 뒤로 못 가므로 중복 선택이 이미 방지됨.
  3. Map<String, List<String>>에 결과 전부 저장할 필요 없음
    • 조합 하나마다 바로 HashSet으로 행 값을 검사.
    • set.size() == relation.length면 유일성 O.
  4. 최소성 검사를 단방향으로 단순화
    • 현재 후보가 이미 확정된 작은 후보키를 포함하는지만 확인하면 됨.
    • 양쪽 containsAll()을 모두 볼 필요 없음.
 
 
 

배운점과느낀점

-졸라 어려웠다 이문제는 생각은 금방끝났는데

생각을 코드로 짜는데 어려웠다

그래도 포기 하지 않고 풀었다는게 좋다.

Set<Character> fkSet = fk.chars()
                        .mapToObj(c -> (char) c)
                        .collect(Collectors.toSet());

                Set<Character> skSet = sk.chars()
                        .mapToObj(c -> (char) c)
                        .collect(Collectors.toSet());
                System.out.println("fkSet: " + fkSet);
                System.out.println("skSet: " + skSet);
                if (skSet.containsAll(fkSet) || fkSet.containsAll(skSet)) {
                    if (fkSet.size() > skSet.size()) {
                        System.out.println("fk가 sk의 부분집합");
                        flag = false;
                        break;
                    }
                }

이부분집합부분에서 또 애를 먹긴했는데 

어쨋든 좋은 프리셋이 있어서 금방 구했다