Programmers
[프로그래머스 Level.3] 모두 0으로 만들기 (월간 코드 챌린지 시즌2) (Java)
Devtraces
2023. 4. 11. 22:18
문제 링크
https://school.programmers.co.kr/learn/courses/30/lessons/76503
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
코딩테스트 연습 > 월간 코드 챌린지 시즌2 > 모두 0으로 만들기
문제 설명
각 점에 가중치가 부여된 트리가 주어집니다. 당신은 다음 연산을 통하여, 이 트리의 모든 점들의 가중치를 0으로 만들고자 합니다.
- 임의의 연결된 두 점을 골라서 한쪽은 1 증가시키고, 다른 한쪽은 1 감소시킵니다.
하지만, 모든 트리가 위의 행동을 통하여 모든 점들의 가중치를 0으로 만들 수 있는 것은 아닙니다. 당신은 주어진 트리에 대해서 해당 사항이 가능한지 판별하고, 만약 가능하다면 최소한의 행동을 통하여 모든 점들의 가중치를 0으로 만들고자 합니다.
트리의 각 점의 가중치를 의미하는 1차원 정수 배열 a와 트리의 간선 정보를 의미하는 edges가 매개변수로 주어집니다. 주어진 행동을 통해 트리의 모든 점들의 가중치를 0으로 만드는 것이 불가능하다면 -1을, 가능하다면 최소 몇 번만에 가능한지를 찾아 return 하도록 solution 함수를 완성해주세요. (만약 처음부터 트리의 모든 정점의 가중치가 0이라면, 0을 return 해야 합니다.)
제한사항
- a의 길이는 2 이상 300,000 이하입니다.
- a의 모든 수는 각각 -1,000,000 이상 1,000,000 이하입니다.
- a[i]는 i번 정점의 가중치를 의미합니다.
- edges의 행의 개수는 (a의 길이 - 1)입니다.
- edges의 각 행은 [u, v] 2개의 정수로 이루어져 있으며, 이는 u번 정점과 v번 정점이 간선으로 연결되어 있음을 의미합니다.
- edges가 나타내는 그래프는 항상 트리로 주어집니다.
입출력 예
a | edges | result |
[-5,0,2,1,2] | [[0,1],[3,4],[2,3],[0,3]] | 9 |
[0,1,0] | [[0,1],[1,2]] | -1 |
입출력 예 설명
입출력 예 #1
- 다음 그림은 주어진 트리의 모든 정점의 가중치를 0으로 만드는 과정을 나타낸 것입니다.

- 2번 정점과 3번 정점을 선택하여 2번 정점은 1 감소시키고, 3번 정점은 1 증가시킵니다. (2번 반복)
- 3번 정점과 4번 정점을 선택하여 4번 정점은 1 감소시키고, 3번 정점은 1 증가시킵니다. (2번 반복)
- 0번 정점과 3번 정점을 선택하여 3번 정점은 1 감소시키고, 0번 정점은 1 증가시킵니다. (5번 반복)
- 모든 정점의 가중치를 0으로 만드는 데 필요한 최소 행동 횟수는 9번이므로, 9를 return 해야 합니다.
입출력 예 #2
- 주어진 트리는 모든 정점의 가중치를 0으로 만드는 것이 불가능하므로, -1을 return 해야 합니다.
나의 코드
import java.util.*;
class Solution {
long answer;
long[] longA;
boolean[] visited;
ArrayList<Integer>[] graph;
public long solution(int[] a, int[][] edges) {
answer = 0;
int total = 0;
longA = new long[a.length];
visited = new boolean[a.length];
graph = new ArrayList[a.length];
for(int i=0; i<a.length; i++) {
total += a[i];
longA[i] = a[i];
graph[i] = new ArrayList<>();
}
if(total != 0) return -1;
for(int i=0; i<edges.length; i++) {
graph[edges[i][0]].add(edges[i][1]);
graph[edges[i][1]].add(edges[i][0]);
}
dfs(0);
return answer;
}
private long dfs(int vertex) {
visited[vertex] = true;
for(int i=0; i<graph[vertex].size(); i++) {
int next = graph[vertex].get(i);
if(!visited[next]) longA[vertex] += dfs(next);
}
answer += Math.abs(longA[vertex]);
return longA[vertex];
}
}
풀이
- 임의의 연결된 두 점을 골라 한쪽은 1 감소시키고 다른 한쪽은 1 증가시키는 것은 한쪽의 값을 다른 쪽으로 옮기는 것과 같다. 따라서, 트리 구조에서 볼 때 최종적으로 몇 번 필요한지 확인하기 위해서는 완전탐색 dfs를 이용하여 리프 노드까지 내려갔다가 올라오면서 자식 노드의 값을 부모 노드에게 옮기고 계산하며 root 노드까지 올라오고 값을 옮길 때마다 그 값을 절댓값으로 하여 카운팅해주면 된다.
- 우선 한쪽은 -1, 한쪽은 1씩 더해주어 모두 0이 되려면 a 배열의 값을 모두 더했을 때 0이 되어야 하기 때문에 a 배열의 값을 모두 더해 0이 되지 않는다면 -1을 반환한다.
- 자식노드의 값을 부모노드에게 더해주면서 올라오기 때문에 수가 충분히 커질 수 있기때문에 longA 배열을 만들어 a 배열의 값을 담아준다.
- 트리 구조의 연결된 간선을 파악하기 위해서 해당 노드의 번호를 인덱스로 하여 연결된 노드의 번호들을 담기 위해 ArrayList를 원소로 갖는 배열 grpah를 만들고 edge를 순회하면서 연결된 노드를 담아준다.
- 또한, 방문한 노드를 체크하기 위해 boolean형 배열 visited를 생성한다.
- 이제 완전탐색 dfs를 진행하면서 리프노드까지 내려가서 자식노드의 값을 부모노드에게 옮기며 해당 값만큼 카운팅한다.
- 루트 노드인 0노드부터 시작하여 해당 노드의 visited를 true로 변경하고 graph를 이용하여 연결된 노드들을 순회하면서 해당 연결된 노드를 방문하지 않았다면 dfs()를 재귀 호출하여 반환 받은 값을 해당 노드의 값에 더한다.
- 재귀 호출을 하며 자식노드를 타고 리프노드까지 내려가서 해당 노드 값의 절댓값 만큼을 answer에 증가시키고 해당 노드의 값을 반환하여 재귀 호출했던 부모노드에게 전달한다.
- 이렇게 dfs를 진행하고 확인해보면 최종적으로 모든 노드의 값을 더한 루트 노드의 값 longA[0]을 확인해보면 0이 되어 있고 값을 옮긴 수만큼(더하고 뺀 횟수) 계속해서 answer에 더해주었으므로 answer를 반환하면 된다.