오늘부터는 본격적으로 medium레벨의 연결 리스트 유형의 문제를 풀어보았습니다. 한 번 살펴볼까요?
Given the root
of a binary tree, flatten the tree into a "linked list":
TreeNode
class where the right
child pointer points to the next node in the list and the left
child pointer is always null
.Example 1:
Input: root = [1,2,5,3,4,null,6]
Output: [1,null,2,null,3,null,4,null,5,null,6]
Example 2:
Input: root = []
Output: []
Example 3:
Input: root = [0]
Output: [0]
Constraints:
[0, 2000]
.-100 <= Node.val <= 100
Follow up:
Can you flatten the tree in-place (with O(1) extra space)?
이번 문제는 이진 트리가 주어졌을 때, 트리를 연결 리스트형태로 평탄화하는 문제입니다. 생성되는 연결 리스트는 동일한 TreeNode
클래스를 사용해야 하며, 오른쪽 자식 포인터(right
)는 리스트에서 다음 노드를 가리켜야 하고, 왼쪽 자식 포인터(left
)는 항상 null
이어야 합니다. 즉, 연결 리스트는 이진 트리의 전위 순회 순서와 동일한 순서여야 합니다.
“주어진 트리 노드를 전위 순회로 나타내라!” 이렇게 이해하면 될 것 같습니다.
입출력을 살펴보도록 하겠습니다.
주어진 이진 트리를 연결 리스트로 반환해야 합니다. 반환 시, 함수의 입력값으로 주어지는 트리를 직접 반환해야 합니다. 즉, 따로 반환값은 존재하지 않습니다.
생성되는 연결 리스트는 트리의 전위 순회 순서대로 노드를 방문했을 때와 동일하기 때문에 전위 순회 알고리즘으로 구현하면 해결될 것 입니다.
전위 순회는 재귀 방법과 스택 방법이 있습니다. 이 두 가지 모두를 사용해서 구현을 진행하도록 하겠습니다.
하지만, 이 방법은 공간 복잡도가 무조건 노드 개수만큼 소요됩니다.
주어진 문제에 O(1)로 구현을 할 수 있겠냐라는 Follow up이 있었습니다.. 이 방법은 Morris Traversal이란 방법이 있는데, 저는 이 방법은 스킵하도록 하겠습니다 (코테에서는 거의 나오지 않는것 같습니다).
그럼 이대로 코드 설계를 해보도록 하겠습니다.
Recursion (Preorder Traversal) 방법
preorder
라는 재귀 함수를 정의하여 트리를 전위 순회합니다.nodes
리스트에 추가합니다.nodes
리스트를 기반으로 트리를 평탄화합니다.nodes[i].right
를 nodes[i+1]
로 설정하여 다음 노드를 연결합니다.nodes[i].left
를 None
으로 설정하여 왼쪽 자식 노드를 제거합니다.Stack (Iterative) 방법
root
노드를 스택에 추가합니다.current
)로 설정합니다.current.right
로 설정하여 연결 리스트를 만듭니다.current.left
를 None
으로 설정하여 왼쪽 자식을 제거합니다.Stack
# Definition for a binary tree node.
class TreeNode(object):
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution(object):
# 2. Stack
def flatten_stack(self, root):
# https://leetcode.com/problems/flatten-binary-tree-to-linked-list/submissions/1470755853
"""
:type root: Optional[TreeNode]
:rtype: None Do not return anything, modify root in-place instead.
"""
if not root:
return
stack = [root]
while stack:
current = stack.pop()
if current.right:
stack.append(current.right)
if current.left:
stack.append(current.left)
if stack:
current.right = stack[-1]
current.left = None
Recursion
# Definition for a binary tree node.
class TreeNode(object):
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution(object):
# 1. Recursion
def flatten_recursive(self, root):
# https://leetcode.com/problems/flatten-binary-tree-to-linked-list/submissions/1470756133
"""
:type root: Optional[TreeNode]
:rtype: None Do not return anything, modify root in-place instead.
"""
if not root:
return
nodes = []
def preorder(node):
if node:
nodes.append(node)
preorder(node.left)
preorder(node.right)
preorder(root)
for i in range(len(nodes)-1):
nodes[i].right = nodes[i + 1]
nodes[i].left = None
nodes
리스트에 저장합니다.right
를 다음 노드로 연결.left
를 None
으로 설정.이번 문제는 이진 트리를 전위 순회로 평탄화하여 연결 리스트로 변환하는 문제였습니다.
두 가지 방법 모두 전위 순회를 기반으로 하며, 공간 복잡도에서 차이를 보입니다. Follow-up에서 요구한 O(1) 공간 복잡도는 Morris Traversal로 해결할 수 있지만, 코테에서는 Iterative 방법이 가장 효율적이고 실용적입니다.
전체 코드는 다음 링크에서 확인할 수 있습니다.
읽어주셔서 감사합니다!