codingtest

표 병합

2026-07-29
2분 분량
JAVA프로그래머스

문제 링크

문제 요약

  • 50 × 50 표에서 UPDATE, MERGE, UNMERGE, PRINT 명령을 순서대로 처리한다
  • MERGE는 두 셀을 하나로 합치고, 값이 있으면 그 값을 유지한다
  • UNMERGE는 선택한 셀이 속한 병합 그룹 전체를 원래 상태로 되돌린다
  • PRINT 결과들을 순서대로 배열에 담아 반환한다

문제 풀이

처음에는 병합된 셀들을 그룹(리스트)으로 따로 관리하면서, 병합될 때마다 그룹끼리 합치는 방식을 생각했다.

하지만 이 방식은 로직이 금방 꼬인다.

MERGE가 여러 번 중첩되면 어떤 셀이 어느 그룹에 속하는지 계속 갱신해야 하고, UNMERGE에서는 선택한 셀 하나가 아니라 그 병합 그룹 전체를 원래 상태로 되돌려야 해서 그룹 구조를 손으로 관리하기가 까다롭다.

이 문제의 핵심은 셀들의 연결 관계를 Union-Find(서로소 집합)로 관리하는 것이다.

각 셀을 parent 배열의 인덱스로 두고, 병합은 두 루트를 연결하는 union으로, 값 조회는 루트를 찾는 find로 처리하면 그룹 관리를 배열 하나로 끝낼 수 있다.

  • MERGE는 두 셀의 루트를 union하고, 값이 있는 쪽의 값을 루트에 남긴다
  • 이때 중요한 점은, UNMERGE는 선택한 셀 하나만 분리하는 게 아니라 같은 루트를 공유하는 모든 셀을 찾아 각각 자기 자신을 부모로 되돌린다는 것이다
  • UNMERGE 이후에는 병합 그룹이 갖고 있던 값을 선택한 셀 하나에만 다시 넣는다
  • PRINT는 루트가 가진 값을 조회해서 비어 있으면 "EMPTY"를 담는다

최종 코드

java
import java.util.*;

class Solution {
    public int[] parent;
    public String[] values;
    
    // 경로 압축을 적용한 루트 찾기
    public int find(int a){
        if(parent[a] == a)
            return a;
        
        return parent[a] = find(parent[a]);
    }
    
    public void union(int a, int b){
        int pa = find(a);
        int pb = find(b);
        
        if(pa==pb)
            return;
        
        String mergedValue;

        // 둘 중 값이 있는 쪽을 병합된 셀의 값으로 유지
        if (!values[pa].equals("")) {
            mergedValue = values[pa];
        } else {
            mergedValue = values[pb];
        }

        parent[pb] = pa;
        values[pa] = mergedValue;
        values[pb] = "";
    }
    
    public int toIdx(int r, int c){
        return (r-1)*50+(c-1);
    }
    
    public String[] solution(String[] commands) {
        List<String> ans = new ArrayList<>();
        parent = new int[50*50];
        values = new String[50*50];
        for(int i=0;i<2500;i++){
            parent[i] = i;
            values[i] = "";
        }
        
        for(int i=0;i<commands.length;i++){
            StringTokenizer st = new StringTokenizer(commands[i]);
            
            String op = st.nextToken();
            
            
            
            if("UPDATE".equals(op)){
                if(st.countTokens() == 3){
                    int r = Integer.parseInt(st.nextToken());
                    int c = Integer.parseInt(st.nextToken());
                    
                    int idx = toIdx(r,c);
                    
                    values[find(idx)] = st.nextToken();
                }else{
                    String value1 = st.nextToken();
                    String value2 = st.nextToken();
                    
                    for(int j=0;j<2500;j++){
                        if(value1.equals(values[j])){
                            values[j] = value2;
                        }
                    }
                }
                
            }else if ("MERGE".equals(op)){
                int r = Integer.parseInt(st.nextToken());
                int c = Integer.parseInt(st.nextToken());
                int r2 = Integer.parseInt(st.nextToken());
                int c2 = Integer.parseInt(st.nextToken());
                
                int idx = toIdx(r,c);
                int idx2 = toIdx(r2, c2);
                
                union(idx, idx2);
            }else if ("UNMERGE".equals(op)){
                int r = Integer.parseInt(st.nextToken());
                int c = Integer.parseInt(st.nextToken());
                int idx = toIdx(r,c);
                
                int root = find(idx);
                String value = values[root];
                
                // 같은 루트를 공유하는 병합 그룹의 멤버를 전부 찾는다
                List<Integer> members = new ArrayList<>();
                for(int j=0;j<2500;j++){
                    if(find(j) == root)
                        members.add(j);
                }
                
                // 그룹 전체를 초기 상태(자기 자신이 부모, 값 없음)로 되돌린다
                for(int m : members){
                    parent[m] = m;
                    values[m] = "";
                }
                
                values[idx] = value;
            }else{
                int r = Integer.parseInt(st.nextToken());
                int c = Integer.parseInt(st.nextToken());
                int idx = toIdx(r,c);
                
                int p = find(idx);
                if(!values[p].equals(""))
                    ans.add(values[p]);
                else
                    ans.add("EMPTY");
            }
        }
        
        String[] answer = new String[ans.size()];
        for(int i=0;i<ans.size();i++){
            answer[i] = ans.get(i);
        }
        return answer;
    }
}

함께 읽으면 좋은 글

코딩 테스트2026-08-23

택배상자

order 배열은 택배 기사님이 원하는 상자 적재 순서다 기존 컨테이너 벨트는 1번부터 순서대로만 꺼낼 수 있다 보조 벨트는 스택처럼 마지막에 넣은 것부터 꺼낼 수 있다 원하는 순서대로 최대한 실을 수 있는 상자 개수를 구하는 문제다 처음에는 기존 벨트를 실제 리스트로 만들어…

코딩 테스트2026-08-23

롤케이크 자르기

topping 배열은 롤케이크에 일렬로 올라간 토핑 번호다 한 지점을 잘라 두 조각으로 나눴을 때, 양쪽 토핑 종류 수가 같아야 공평하게 나눈 것이다 공평하게 자를 수 있는 방법의 수를 구하는 문제다 처음에는 자르는 위치마다 왼쪽과 오른쪽 배열을 나눠서 각각 서로 다른 토핑…

코딩 테스트2026-08-23

할인 행사

want, number 배열로 정현이가 원하는 제품과 수량을 표현한다 discount 배열은 XYZ 마트가 매일 할인하는 제품 목록이다 연속된 10일 동안 할인 제품 종류와 수량이 want, number와 정확히 일치해야 회원가입할 수 있다 그런 시작일이 총 몇 번 있는지…

코딩 테스트2026-08-23

숫자 변환하기

자연수 x를 y로 바꾸는 데 x+n, x2, x3 세 가지 연산을 쓸 수 있다 x를 y로 바꾸는 최소 연산 횟수를 구하는 문제다 만들 수 없으면 -1을 반환한다 처음에는 x에서 시작해서 세 가지 연산을 재귀적으로 다 시도해보고 y에 도달하는 경로 중 가장 짧은 걸 고르면…