문제 설명
이진 트리와 거리 값 d가 하나 주어집니다. 서로 다른 두 리프(잎) 노드로 이루어진 쌍은, 두 노드 사이의 최단 경로 길이가 d보다 작거나 같을 때 '좋은(good) 쌍'으로 정의됩니다.
예를 들어 아래와 같은 이진 트리가 있다고 가정해 보겠습니다.

이때 거리 d = 4라면 정답은 2입니다. 그 이유는 (8, 7)과 (5, 6) 두 쌍의 경로 길이가 각각 2로 조건을 만족하지만, (7, 5)나 (8, 6) 같은 나머지 쌍들은 경로 길이가 5로 d = 4보다 크기 때문에 좋은 쌍이 될 수 없습니다.
해결 접근 방법
이 문제는 후위 순회(post-order traversal) 기반의 재귀 함수를 활용해 효율적으로 해결할 수 있습니다. 핵심 아이디어는 각 노드에서 자손 리프 노드까지의 거리 정보를 리스트로 관리하고, 왼쪽 서브트리와 오른쪽 서브트리의 리프 노드들을 서로 조합하며 조건을 만족하는 쌍을 세는 것입니다.
구체적인 단계는 다음과 같습니다.
정답 카운터 sol을 0으로 초기화합니다.
루트 노드를 인자로 받는 util() 함수를 정의합니다.
루트가 null이면 빈 리스트를 반환합니다.
루트가 리프 노드라면 [0, 0] 형태의 항목 하나를 담은 배열을 반환합니다. 여기서 두 번째 요소는 현재 노드부터 해당 리프까지의 거리를 의미합니다.
그 외의 경우에는 다음을 수행합니다.
왼쪽 자식에 대한 util() 호출 결과를 l에 저장합니다.
오른쪽 자식에 대한 util() 호출 결과를 r에 저장합니다.
l과 r의 모든 항목 n에 대해 n[1] 값을 1씩 증가시킵니다. 현재 노드를 한 단계 거치므로 거리가 1 늘어나기 때문입니다.
r의 각 항목 n과 l의 각 항목 n1에 대해 n[1] + n1[1] <= d를 만족하면 sol을 1 증가시킵니다. 즉, 왼쪽과 오른쪽 서브트리에 속한 리프 노드 쌍의 경로 길이가 d 이하인 경우를 셉니다.
병합된 리스트 l + r을 상위 호출로 반환합니다.
메인 메서드에서 util(root)를 호출한 뒤 sol을 반환합니다.
이 알고리즘의 시간 복잡도는 최악의 경우 O(n²)이며, 공간 복잡도는 재귀 스택과 거리 리스트 저장을 위해 O(n)입니다.
아래 예제 구현을 통해 더 잘 이해해 보겠습니다.
예제
class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right class Solution: def __init__(self): self.sol = 0 def solve(self, root, d): def util(root): if not root: return [] if not root.left and not root.right: return [[0, 0]] else: cur = [] l = util(root.left) r = util(root.right) for n in l: n[1] += 1 for n in r: n[1] += 1 for n in r: for n1 in l: if n[1] + n1[1] <= d: self.sol += 1 return l+r util(root) return self.sol root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.right = TreeNode(4) root.left.right.left = TreeNode(8) root.left.right.right = TreeNode(7) root.right.left = TreeNode(5) root.right.right = TreeNode(6) d = 4 ob = Solution() print(ob.solve(root, d))
입력
root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.right = TreeNode(4) root.left.right.left = TreeNode(8) root.left.right.right = TreeNode(7) root.right.left = TreeNode(5) root.right.right = TreeNode(6) d = 4
출력
2
실행 결과로 2가 출력되는데, 이는 경로 길이가 d = 4 이하인 좋은 리프 노드 쌍이 (8, 7)과 (5, 6)으로 총 두 개 존재한다는 의미입니다.