아래 그림처럼 부모 노드인 1번을 기준으로 1-2-3-4 로 연결되어 있는 트리라고 가정해보자.
1
|
2
|
3
|
4
해당 게임에서 언제나 내가 먼저 시작한다고 했을 때,
4 -> 3 : 나
3 -> 2 : 상대
2 -> 1(탈출) : 나
이렇게 되면 상대가 움직일 말이 없이 때문에 무조건 내가 승리하게 된다.
따라서 부모노드부터 리프노드의 길이의 합이 홀수면 나의 승리, 짝수면 상대의 승리이다.
let input = require("fs").readFileSync(0, 'utf-8').toString().trim().split("\n");
const N = input.shift() * 1;
const trees = Array.from({length: N + 1}, _ => new Array());
const visited = Array(N + 1).fill(false);
input = input.map(v => v.split(' ').map(Number));
input.forEach(v => {
const [start, end] = v;
trees[start].push(end);
trees[end].push(start);
});
let answer = 0;
const dfs = (now, depth) => {
visited[now] = true;
if (now !== 1 && trees[now].length === 1) {
answer += depth;
return;
}
for (const next of trees[now]) {
if (!visited[next]){
dfs(next, depth + 1);
}
}
};
dfs(1, 0);
console.log(answer % 2 === 1 ? 'Yes' : 'No');
위의 풀이를 완성하기 전까지 수많은 시도들이 있었다.
우선 아래 두 코드를 비교해보자.
통과
let input = require("fs").readFileSync(0, 'utf-8').toString().trim().split("\n");
const N = input.shift() * 1;
const trees = Array.from({length: N + 1}, _ => new Array());
const visited = Array(N + 1).fill(false);
실패
let input = require("fs").readFileSync(0, 'utf-8').toString().trim().split("\n");
const N = Number(input.shift());
const trees = Array.from({length: N + 1}, _ => new Array());
const visited = Array(N + 1).fill(false);
두 코드의 가장 큰 차이점은 입력의 N을 받을 때 실패의 경우 Number()를 이용했다는 것이다.
Number()를 사용할 경우 내부적으로 타입 체크와 복잡한 과정을 거치게 된다고 한다.
하지만 * 1 연산의 경우 단순한 강제 형변환을 진행한다고 한다.
Number()내부 동작
- 입력 유형 확인
- undefined는 NaN으로 변환됩니다.
- null은 0으로 변환됩니다.
- boolean 값은 true는 1, false는 0으로 변환됩니다.
- 이미 number 타입인 경우 그대로 반환됩니다.
- 문자열 변환 과정 (가장 복잡한 부분):
- 앞뒤 공백을 제거합니다.
- 빈 문자열은 0으로 변환됩니다.
- 16진수 형식(0x로 시작)을 확인하고 파싱합니다.
- 8진수나 2진수 형식을 확인하고 파싱합니다.
- 문자열이 유효한 숫자 형식인지 검증하고, 소수점, 지수 표기법 등을 처리합니다.
- 숫자로 변환할 수 없는 문자열은 NaN을 반환합니다.
- 객체 변환:
- valueOf() 메서드를 호출하여 원시 값을 얻으려고 시도합니다.
- 원시 값을 얻지 못하면 toString() 메서드를 호출합니다.
- 이후 얻은 원시 값으로 다시 위의 과정을 반복합니다.
- 정밀도 및 범위 처리:
- JavaScript는 IEEE 754 부동소수점 표준을 사용하므로, 변환된 숫자가 이 범위를 벗어나면 Infinity 또는 -Infinity로 처리합니다.

따라서 동일한 코드에 Number()를 사용할 경우 위의 결과처럼 시간 초과가 발생한다.
그 외에도
Array.from({length: N + 1}, _ => new Array());의 경우와
Array.from({length: N + 1}, _ => []);를 비교해보면
new Array()로 할당할 경우가 더 빠르게 나왔지만, 이부분은 환경에 따라 다르다고 한다.
또한 이 문제말고 다양한 원인으로 시간 초과가 발생 했는데 그것을 이야기하면 다음과 같다.
1. 리프 노드를 미리 찾는 로직 수행 => 시간 초과 발생.
2.전체 길이가 홀수 인지 계산보다홀수 길이의 갯수가 홀수개인 경우 계산이 더 빠른 것 같다.
문제를 찾기 위해 너무 많은 시도를 진행했다.
Number() 로직 하나 때문에 시간 초과가 발생하는 것을 보면 시간이 생각보다 타이트하게 잡혀있는 것 같다.
