Validate Binary Search Tree
BST로 추정되는 트리의 루트 노드가 주어졌을 때, 이게 진짜 BST인지 확인하자.
BST는 다음의 Search Property를 만족해야 한다:
- 노드의 왼쪽 서브트리는 노드의 키 값보다 작은 값만 담아야 한다.
- 노드의 오른쪽 서브트리는 노드의 키 값보다 큰 값만 담아야 한다.
- 왼쪽과 오른쪽 서브트리도 역시 위의 조건을 재귀적으로 만족해야 한다.
노드의 개수는 1~10,000개, 노드의 값은 32비트 정수가 포함할 수 있는 모든 범위에 걸쳐 있다.
재귀적으로 범위 나누기
노드를 중심으로 범위를 나눈다고 생각해보자. 조건에 따라 왼쪽 서브트리의 모든 노드의 키 값은 현재 노드의 키 값보다 작아야 한다. 오른쪽 서브트리는 이와 반대로 커야 한다. 이걸 범위로 생각해보자. 초기의 범위는 \([-\infty, \infty]\) 이다. 루트는 항상 이 범위 안에 들어간다. 루트를 기준으로 왼쪽 서브트리의 범위는 조건에 따라 \([-\infty, root.val)\)이 된다. 자연스럽게 오른쪽 서브트리의 범위는 \((root.val, \infty]\) 임을 알 수 있다. 이런식으로 재귀적으로 범위를 계속 쪼개가면서, 현재 노드의 값이 이 범위 안에 들어가는지를 확인하면 Search Property를 만족하는지 알 수 있다.
def isValidBST(root):
def validate(node, low=float('-inf'), high=float('inf')):
if node is None:
return True
return (low < node.val < high) and validate(node.left, low, node.val) and validate(node.right, node.val, high)
return validate(root)
- 파이썬의
float('inf')
와float('-inf')
를 이용해서 손쉽게 무한소와 무한대를 표현할 수 있다. - 노드가 null이면 자연스럽게 만족한다고 할 수 있따.
트리의 중위 순회 순서 이용하기
다른 방법으로는 트리의 중위 순회를 이용하는 방법이 있다. 트리의 순회는 기본적으로 왼쪽 -> 오른쪽으로 이뤄지고, 루트 노드를 언제 방문할 것인지에 따라 다음 세 가지가 있다.
- Preorder: root -> left -> right
- Inorder: left -> root -> right
- Postorder: left -> right -> root
이때 트리가 BST라면, Inorder 순위는 자연스럽게 Search Property를 만족하는 정렬된 순서로 노드를 방문하게 된다. 이 성질을 이용해보자.
먼저 이전 값을 저장해둘 필요가 있다. 초기값은 \(-\infty\)이다. 이 값은 항상 32비트 최소값보다 작으므로 트리에 어떤 값이 담겨 있든 중위 순회의 첫번째 방문 노드의 키 값보다 작을 것이다. 이후 중위 순회 순서대로 진행하면서, 이 이전 값보다 지금 노드 값을 비교하고 업데이트한다. 이렇게 끝까지 진행하면서 하나라도 순서가 이상한게 있는지 체크하면 된다.
def isValidBST(root):
prev = float('-inf')
def inorder(node):
nonlocal prev
if node is None:
return True
if not inorder(node.left):
return False
if not (prev < node.val):
return False
prev = node.val
return inorder(node.right)
return inorder(root)
- 파이썬은 Expression 안에 Assignment를 쓸 수 없다. 그래서 리턴
한줄에 조건을 우겨넣으면서 동시에
prev
값을 업데이트할 수 없다. 위 코드처럼 하나씩 차근차근 해결하자. - 중위 순회의 정의에 따라 왼쪽을 먼저 확인하고, 그 다음 지금 노드를
체크한다. 이때 반드시
prev
값을 업데이트해줘야 한다. 그래야 올바른 체크가 된다.