일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 일급 컬렉션
- 일급 객체
- java
- Dependency Injection
- Volatile
- Google OAuth
- middleware
- nestjs
- OAuth 2.0
- factory
- spring security
- builder
- synchronized
- Spring
- lombok
- Today
- Total
HJW's IT Blog
[프로그래머스 / JAVA / Javascript] 표병합 본문
UnionFind란?
Union Find 란 집합의 원소들이 어느 그룹에 속해 있는지를 빠르게 찾고 (Find) , 합치는 (Union) 연산을 효율적으로 처리하도록 설계된 자료구조이다.
주로 서로소 집합(Disjoint Set) 문제를 해결되는데 사용되는데, 서로소 집합이란 공통된 원소가 없는 두 집합을 의미한다.
기본 연산
Union Find 는 이전에 말했듯이, Find 와 Union 연산을 지원해야 한다.
- Find : 주어진 원소가 속한 대표 를 찾는 연산이다.
int[] find(int x, int y){
int[] root = this.parents[x][y];
if(root[0] == x && root[1] == y) return new int[]{x,y};
int[] newParent = this.find(root[0], root[1]);
this.parents[x][y] = newParent;
return newParent;
}
Union Find 는 이러한 연산을 더욱 효율적으로 하기 위해 경로 압축 을 사용한다. Find 연산시, 현재 노드가 최상위 노드가 아니라면 재귀적으로 부모를 찾아간다. 해당 과정에서 노드의 부모를 최상위 노드로 갱신하여 준다.
- Union : 두 집합을 하나의 집합으로 합치는 연산으로, 두 집합의 대표자를 찾아 비교하고, 정의된 규칙에 따라 한 집합을 다른 집합의 subset 으로 만든다.
문제 풀이
이 문제는 50 x 50 의 고정 크기 표에서 여러 명령어를 수행하는 프로그램을 작성하는것이 목표다. 표의 각 cell 은 문자열 값을 가질 수 있고, 각 cell 간의 병합 혹은 해제를 수행할 수 있다. 병합된 cell 은 하나의 cell 처럼 동작하며, 병합 된 cell 중 하나라도 update 를 하게 되면, 나머지 cell 들도 동일한 값을 가지게 된다.
그렇다면 어떻게 이 문제를 풀어야 할까?
우선 우리가 관리해야 할 정보는 2가지 이다.
- 각 cell 이 속한 집합 (병합)
- 각 cell 의 상태 (값)
문제의 특성상 merge, unmerge, update 가 여러번 일어날 수 있는데, 그렇다면 이러한 연산들을 빠르게 수행할 수 있는 자료구조인 Union Find를 사용하면 좋겠다는 것을 알 수 있다.
각 cell 을 node로, merge 된 cell 들은 하나의 집합으로 관리하게 되면, 해당 문제를 효율적으로 풀 수 있다.
JAVA
import java.util.ArrayList;
class UnionFind{
int[][][] parents; // 각 cell 의 부모 좌표를 저장한다. parents[i][j][0] 은 i, j cell 의 부모 x 좌표를 나타낸다
String[][] value; // 각 cell 이 실제로 가지는 값을 나타낸다.
UnionFind(){
this.parents = new int[51][51][2];
this.value = new String[51][51];
for(int i = 0; i<51; i++){
for(int j = 0; j<51; j++){
this.parents[i][j][0] = i;
this.parents[i][j][1] = j;
this.value[i][j] = "";
}
}
}
int[] find(int x, int y){
int[] root = this.parents[x][y];
if(root[0] == x && root[1] == y) return new int[]{x,y};
int[] newParent = this.find(root[0], root[1]);
this.parents[x][y] = newParent; // 경로 압축
return newParent;
}
void merge(int x1, int y1, int x2, int y2){
int[] root1 = this.find(x1, y1);
int[] root2 = this.find(x2, y2);
if(root1[0] == root2[0] && root1[1] == root2[1]) return;
if(!this.value[root1[0]][root1[1]].equals("")){
this.parents[root2[0]][root2[1]] = new int[]{root1[0], root1[1]};
}else{
this.parents[root1[0]][root1[1]] = new int[]{root2[0], root2[1]};
}
}
void unmerge(int x, int y){
int[] root = this.find(x,y);
String originalValue = this.value[root[0]][root[1]];
ArrayList<int[]> toUnmerge = new ArrayList<>();
for(int i = 0; i<51; i++){
for(int j = 0; j<51; j++){
int[] tmp = this.find(i, j);
if(root[0] == tmp[0] && root[1] == tmp[1]){
int[] cur = new int[]{i,j};
toUnmerge.add(cur);
}
}
}
for(int[] un : toUnmerge){
this.parents[un[0]][un[1]][0] = un[0];
this.parents[un[0]][un[1]][1] = un[1];
this.value[un[0]][un[1]] = "";
}
this.value[x][y] = originalValue;
}
void updateValue(String oldVal, String newVal){
for(int i = 0; i<51; i++){
for(int j = 0; j<51; j++){
if(this.value[i][j].equals(oldVal)){
this.value[i][j] = newVal;
}
}
}
}
void updateCell(int x, int y, String value){
int[] root = this.find(x, y);
this.value[root[0]][root[1]] = value;
}
String print(int x, int y){
int[] root = this.find(x,y);
return this.value[root[0]][root[1]].equals("") ? "EMPTY" : this.value[root[0]][root[1]];
}
}
class Solution {
public String[] solution(String[] commands) {
UnionFind uf = new UnionFind();
ArrayList<String> ans = new ArrayList<>();
for(String command: commands){
String[] c = command.split(" ");
if(c[0].equals("UPDATE")){
if(c.length == 4){
int x = Integer.parseInt(c[1]);
int y = Integer.parseInt(c[2]);
uf.updateCell(x, y, c[3]);
}else{
uf.updateValue(c[1], c[2]);
}
}else if(c[0].equals("MERGE")){
int x1 = Integer.parseInt(c[1]);
int y1 = Integer.parseInt(c[2]);
int x2 = Integer.parseInt(c[3]);
int y2 = Integer.parseInt(c[4]);
uf.merge(x1,y1,x2,y2);
}else if(c[0].equals("UNMERGE")){
int x = Integer.parseInt(c[1]);
int y = Integer.parseInt(c[2]);
uf.unmerge(x, y);
}else if(c[0].equals("PRINT")){
int x = Integer.parseInt(c[1]);
int y = Integer.parseInt(c[2]);
String res = uf.print(x, y);
ans.add(res);
}
}
String[] answer = new String[ans.size()];
for(int i = 0; i<answer.length; i++){
answer[i] = ans.get(i);
}
return answer;
}
}
JAVASCRIPT
/*
UPDATE r c value : (r, c) 의 cell 을 value로
UPDATE value1 value2 : value1 인 모든 cell 을 value2 로
MERGE r1 c1 r2 c2 : (r1, c1) 과 (r2, c2) cell 을 병합
UNMERGE r c : (r, c) cell 병합 해제
PRING r c : (r, c) 출력
*/
class UnionFind{
constructor(){
this.parents = [];
this.value = [];
for(let i = 0; i<51; i++){
this.parents[i] = [];
this.value[i] = [];
for(let j = 0; j<51; j++){
this.value[i][j] = "";
this.parents[i][j] = [i,j];
}
}
}
find(x, y){
const [parentX, parentY] = this.parents[x][y];
if(parentX === x && parentY === y) return [x, y];
return this.parents[x][y] = this.find(parentX, parentY);
}
merge(x1, y1, x2, y2){
const [rootX1, rootY1] = this.find(x1, y1);
const [rootX2, rootY2] = this.find(x2, y2);
if(rootX1 === rootX2 && rootY1 === rootY2) return;
if(this.value[rootX1][rootY1] !== ""){
this.parents[rootX2][rootY2] = [rootX1, rootY1];
}else{
this.parents[rootX1][rootY1] = [rootX2, rootY2];
}
}
unmerge(x, y){
const [rootX, rootY] = this.find(x, y);
const originalValue = this.value[rootX][rootY];
const toUnmerge = [];
for(let i = 0; i<51; i++){
for(let j =0; j<51; j++){
let tmp = this.find(i,j);
if(rootX === tmp[0] && rootY === tmp[1]) toUnmerge.push([i,j]);
}
}
toUnmerge.forEach(([i,j]) => {
this.parents[i][j] = [i,j];
this.value[i][j] = "";
})
this.value[x][y] = originalValue;
}
updateValue(oldVal, newVal){
for(let i = 0; i<51; i++){
for(let j = 0; j<51; j++){
if(this.value[i][j] === oldVal) this.value[i][j] = newVal;
}
}
}
updateCell(x, y, value){
const [rootX, rootY] = this.find(x,y);
this.value[rootX][rootY] = value;
}
print(x, y){
const [rootX, rootY] = this.find(x, y);
return this.value[rootX][rootY] === "" ? "EMPTY" : this.value[rootX][rootY];
}
}
function solution(commands) {
var answer = [];
const uf = new UnionFind();
for(let command of commands){
let c = command.split(" ");
if(c[0] === "UPDATE"){
if(c.length === 4){
uf.updateCell( parseInt(c[1],10), parseInt(c[2],10), c[3]);
}else{
uf.updateValue(c[1], c[2]);
}
}else if(c[0] === "MERGE"){
uf.merge(
parseInt(c[1], 10),
parseInt(c[2], 10),
parseInt(c[3], 10),
parseInt(c[4], 10)
);
}else if(c[0] === "UNMERGE"){
uf.unmerge(
parseInt(c[1], 10),
parseInt(c[2], 10)
);
}else{
answer.push(uf.print(parseInt(c[1], 10) , parseInt(c[2], 10)));
}
}
return answer;
}
'Algorithm' 카테고리의 다른 글
[BOJ/JAVA] 주사위 굴리기 (2) | 2024.11.20 |
---|---|
[BOJ : JAVA] 아기상어 (1) | 2024.11.18 |
[프로그래머스/c++] 사라지는 발판 (0) | 2024.09.23 |
[프로그래머스/C++] 코딩테스트 공부 (0) | 2024.09.19 |
[Programmers/C++] 등산코스 정하기 (1) | 2024.09.16 |