업데이트:

3 분 소요

문제

https://leetcode.com/problems/binary-tree-right-side-view/description/?envType=study-plan-v2&envId=top-interview-150

주어진 트리를 오른쪽에서 바라보자 !
트리를 오른쪽에서 바라봤을 때 보이는 노드들을 위에서부터 순서대로 반환해야하는 문제.

풀이

주어진 입력인 트리는 불균형한 트리일 가능성이 크다.
따라서 가장 오른쪽의 노드들 뿐만이 아니라 왼쪽으로 삐져나온 트리들도 볼 수 있다.

이점을 생각했을 때 오른쪽부터 노드를 확인해야한다.
또한 오른쪽 노드를 가장 먼저 확인하기 때문에 전위 순회를 택했다.
(hist에 추가할 노드의 우선순위는 가장 오른쪽에 있는 노드이기 때문)

  1. rootNone인 경우를 제외하기 위해 이를 확인한다.
  2. 결과를 담을 배열 hist에 해당 노드를 탐색했을 때의 깊이를 알기위한 depth 를 같이 저장한다.
  3. 전위 순회이므로 가장 먼저 현재 노드를 판단한다.
    1. hist의 가장 마지막에 들어있는 노드의 깊이가 현재 깊이보다 낮은 경우 현재 노드와 깊이를 추가한다.
  4. 오른쪽 노드에 대해 dfs를 수행한다.
  5. 왼쪽 노드에 대해 dfs를 수행한다.
  6. 3 ~ 5의 과정을 재귀적으로 수행한다.
  7. 얻어낸 결과 리스트를 반환한다.

시도 1

실행시간 : 15ms
메모리 : 13.3MB

class Solution(object):
    def rightSideView(self, root):
        if root != None : 
            hist = [[root.val , 0]]

            def dfs(start, depth = 0):
                if start == None:
                    return
                
                if hist[-1][1] < depth: 
                    hist.append([start.val, depth])
                depth += 1
                
                dfs(start.right, depth)                
                dfs(start.left, depth)

            dfs(root)
        
            return list(map(lambda x : x[0], hist))
        return []        

개선 1

실행시간 : 17ms
메모리 : 13.3MB

이전에 트리에 대해 살펴 보았을 때 DFS보다는 BFS가 적절하다는 생각을 했다.
(링크에서 적진 않았지만 BFS는 계층적으로 순회할 때 유리하다.)

따라서 너비 우선 탐색으로 또한 구현해 보았다.
위 로직과 처음 None을 확인하는 것 외로는 완전히 같지만, 확실히 깊이를 하나씩 확인해 나아가는 너비 우선탐색이 생각하기 편했다.

하지만 실행시간은 완전히 차이가 없다 라고 말할 수준의 차이를 보였다.
사실 확연한 성능을 확인할 수 없는 이유는 1000 개 정도의 적은 입력 때문이겠지만, 성능 차를 눈으로 확인할 수 없는 것은 확실히 아쉬운 점이었다.

from collections import deque
class Solution(object):
    def rightSideView(self, root):
        depth = 0
        hist = []

        def bfs(start):
            q = deque()
            
            if start != None:
                hist.append([start, depth])
                q.append([start, depth])
            
            while q:
                x, d = q.popleft()
                
                if x.right != None:
                    q.append([x.right, d + 1])
                    
                if x.left != None:
                    q.append([x.left, d + 1])
                

                if d > hist[-1][1]:
                    hist.append([x, d])
        bfs(root)

        return list(map(lambda x : x[0].val, hist))

생각 & 정리

아쉬운 마음에 간략하고 투박하게 나마 실험해보기로했다.

이렇게
from collections import deque
import time
import sys
sys.setrecursionlimit(10**9)

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def create_binary_tree(num_nodes):
    root = TreeNode(1)
    current_node = root

    for i in range(2, num_nodes + 1):
        new_node = TreeNode(i)

        if current_node.left is None:
            current_node.left = new_node
        else:
            current_node.right = new_node
            current_node = current_node.left

    return root
num_nodes = 18000
root = create_binary_tree(num_nodes)

dpast = time.time()

def rightSideView(root):
    depth = 0
    hist = []

    def bfs(start):
        q = deque()

        if start != None:
            hist.append([start, depth])
            q.append([start, depth])

        while q:
            x, d = q.popleft()

            if x.right != None:
                q.append([x.right, d + 1])

            if x.left != None:
                q.append([x.left, d + 1])

            if d > hist[-1][1]:
                hist.append([x, d])

    bfs(root)

    return list(map(lambda x: x[0].val, hist))

rightSideView(root)

print(f"bfs 결과 -> {time.time() - dpast}")

past = time.time()

def rightSideView(root):
    if root != None:
        hist = [[root.val, 0]]

        def dfs(start, depth=0):
            if start == None:
                return

            if hist[-1][1] < depth:
                hist.append([start.val, depth])
            depth += 1

            dfs(start.right, depth)

            dfs(start.left, depth)

        dfs(root)

        return list(map(lambda x: x[0], hist))
    return []
rightSideView(root)
print(f"dfs 결과 -> {time.time() - past}")  

결과는 다음과 같았다.

노드 갯수 : 1000
bfs 결과 -> 0.00039505958557128906
dfs 결과 -> 0.0004353523254394531  
---------------------------------  
노드 갯수 : 5000
bfs 결과 -> 0.0013880729675292969
dfs 결과 -> 0.001750946044921875
---------------------------------
노드 갯수 : 10000
bfs 결과 -> 0.0033609867095947266
dfs 결과 -> 0.004140138626098633
---------------------------------
노드 갯수 : 20000
bfs 결과 -> 0.0058476924896240234
dfs 결과 -> 0.007363080978393555

입력이 커지면 커질수록 두 순회방법에 따른 시간 차이가 점점 커졌다. 20000개도 여전히 작은 입력이지만,
10만개 하면 터진다

결과는 항상 같았다.

순회방법만 적절히 선택해도 큰차이가 벌어질 수 있다는 것을 새삼 크게 깨달았다.

카테고리:

업데이트:

댓글남기기