-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: cloud <[email protected]>
- Loading branch information
1 parent
a616990
commit e26eb35
Showing
1 changed file
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
--- | ||
slug: 2024-01-31-LC-450 | ||
title: LC#450 (Delete Node in a BST) | ||
summary: LC#450 (Delete Node in a BST) | ||
author: Sanghun lee | ||
date: 2024-01-31 11:33:00 +0800 | ||
categories: [LeetCode, BST, Successor, Predecessor, Inorder] | ||
folder: [post-algorithm] | ||
tags: [Algorithm] | ||
math: true | ||
mermaid: true | ||
image: | ||
src: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/LeetCode_Logo_black_with_text.svg/640px-LeetCode_Logo_black_with_text.svg.png | ||
width: 850 | ||
height: 585 | ||
--- | ||
|
||
# 문제 | ||
|
||
Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST. | ||
|
||
Basically, the deletion can be divided into two stages: | ||
|
||
Search for a node to remove. | ||
If the node is found, delete the node. | ||
|
||
```md | ||
Input: root = [5,3,6,2,4,null,7], key = 3 | ||
Output: [5,4,6,2,null,null,7] | ||
|
||
Explanation: Given key to delete is 3. So we find the node with value 3 and delete it. | ||
One valid answer is [5,4,6,2,null,null,7], shown in the above BST. | ||
|
||
Please notice that another valid answer is [5,2,6,null,4,null,7] and it's also accepted. | ||
``` | ||
|
||
Constraints: | ||
|
||
The number of nodes in the tree is in the range [0, 104]. | ||
|
||
-105 <= Node.val <= 105 | ||
|
||
Each node has a unique value. | ||
|
||
root is a valid binary search tree. | ||
|
||
-105 <= key <= 105 | ||
|
||
# 풀이 | ||
|
||
Solving카테고리가 LeetHub라는 익스텐션을 사용하게 되며 다른 별도의 레포에 문제가 solve상태가 되면 올라가게 만든 뒤로 조금 방치해놓은 카테고리가 된 것 같다. | ||
|
||
블로그에 알고리즘 글을 쓰는 것은 오랜만이지만.. 나름 시간 될때 한번씩은 알고리즘을 계속 풀고 있었다. | ||
|
||
오늘은 처음으로 보게 된 문제이고 정리할 개념도 있는 것 같아 글을 작성해본다. | ||
|
||
처음에는 간단하게 노드의 값을 null로 해결해버리면 되지 않을까 하였으나 역시나 삭제를 할때 위치를 바꾸고 정렬을 해주는 것이 당연히 필요한 문제이다. | ||
BST 구조를 유지하는게 조건이니 당연한 것이었다. | ||
|
||
아래는 해설을 보며 처음 접해본 successor, predecessor에 관한 개념의 정리와 이 개념으로 문제를 해결한 방식이다. | ||
|
||
`Inorder traversal of BST is an array sorted in ascending order.` | ||
|
||
BST에서 중위 순회를 하게 되면 구조의 특성으로 인해 오름차순으로 정렬을 할 수 있다. | ||
|
||
```javascript | ||
//inorder : left -> node -> right | ||
const inorder = function (root, arr) { | ||
if (!root) return arr; | ||
inorder(root.left, arr); | ||
arr.push(root.val); | ||
inorder(root.right, arr); | ||
return arr; | ||
}; | ||
``` | ||
|
||
여기에서 Successor(후임자), Predecessor(선행자)라는 개념을 활용할 수 있게 된다. | ||
|
||
Successor: 선택한 노드의 오른쪽 서브트리중 가장 작은 값을 가지는 노드를 의미한다. 중위순회를 한 상태에서 보게 된다면 successor는 해당 노드의 바로 다음 값이다. | ||
|
||
Predecessor: 선택한 노드의 값보다 작으면서 가장 큰 값을 가지는 노드를 의미한다(↔ Successor). 중위순회를 한 상태에서 보게 된다면 Predecessor는 해당 노드의 바로 전 값이다.(값이 같다면 본인이 될 수도 있지만 논외로하자) | ||
|
||
따라서 이러한 개념을 가지고 난 뒤, 해당 BST구조에서 해당되는 형태의 노드를 가져올 수 있는 간단한 수도코드를 살펴보자. | ||
|
||
```jsx | ||
/** | ||
* Definition for a binary tree node. | ||
* function TreeNode(val, left, right) { | ||
* this.val = (val===undefined ? 0 : val) | ||
* this.left = (left===undefined ? null : left) | ||
* this.right = (right===undefined ? null : right) | ||
* } | ||
*/ | ||
// one step right and then left till you can | ||
const successor = function (root) { | ||
root = root.right; | ||
while (root.left !== null) root = root.left; | ||
return root; | ||
}; | ||
``` | ||
|
||
```jsx | ||
/** | ||
* Definition for a binary tree node. | ||
* function TreeNode(val, left, right) { | ||
* this.val = (val===undefined ? 0 : val) | ||
* this.left = (left===undefined ? null : left) | ||
* this.right = (right===undefined ? null : right) | ||
* } | ||
*/ | ||
// one step left and then right till you can | ||
const predecessor = function (root) { | ||
root = root.left; | ||
while (root.right !== null) root = root.right; | ||
return root; | ||
}; | ||
``` | ||
|
||
여기까지 선행 지식을 습득하였으면 본 문제를 해결할 실마리가 생기는 것 같다. | ||
|
||
BST의 구조를 유지하면서 key값을 지울 때 successor, predecessor를 잘 활용한다면 노드를 바꿔치고 삭제하기가 수월해지기 때문이다. | ||
|
||
문제의 요구사항은 입력받은 이진탐색 트리와 key를 통해 key에 해당하는 노드를 지운 BST를 반환해달라는 것이고, 이 때 위의 개념들을 활용할 수 있게 된다. | ||
|
||
문제해결 시 key가 위치할 세가지 케이스를 나눠볼 수 있게 된다. | ||
|
||
1. key가 leaf인 경우 | ||
|
||
1. node = null로 선언하여 간단하게 해결 | ||
|
||
2. leaf가 아니고 오른쪽 subtree에 있을 경우 → Successor를 찾아서 현재의 노드 값을 Sucessor값으로 변경한 후 실제 Successor노드를 날려버리는 방법 | ||
|
||
3. leaf가 아니고 왼쪽 subtree에 있을 경우 → Predecessor를 찾아 현재의 노드 값을 Predecessor로 변경한 후 실제 Predecessor노드를 날려버리는 방법 return | ||
|
||
아래는 위 케이스를 토대로 작성된 해결법이다. | ||
|
||
```jsx | ||
/** | ||
* Definition for a binary tree node. | ||
* function TreeNode(val, left, right) { | ||
* this.val = (val===undefined ? 0 : val) | ||
* this.left = (left===undefined ? null : left) | ||
* this.right = (right===undefined ? null : right) | ||
* } | ||
*/ | ||
/** | ||
* @param {TreeNode} root | ||
* @param {number} key | ||
* @return {TreeNode} | ||
BFS | ||
left is smaller right is bigger | ||
if key is leaf node -> ? | ||
if key is parent node -> ? | ||
how to remove node -> 1. set val null ? 2. find node -> sort every node again without find node | ||
*/ | ||
|
||
const successor = function (root) { | ||
root = root.right; | ||
while (root.left) root = root.left; | ||
return root.val; | ||
}; | ||
|
||
const predecessor = function (root) { | ||
root = root.left; | ||
while (root.right) root = root.right; | ||
return root.val; | ||
}; | ||
|
||
var deleteNode = function (root, key) { | ||
if (!root) return null; | ||
// 오른쪽 서브트리에서 삭제되는 경우 | ||
if (key > root.val) root.right = deleteNode(root.right, key); | ||
// 왼쪽 서브트리에서 삭제되는 경우 | ||
else if (key < root.val) root.left = deleteNode(root.left, key); | ||
// 현재 노드를 삭제해야하는 경우 | ||
else { | ||
// 리프인 경우 | ||
if (!root.left && !root.right) root = null; | ||
// right subtree에서 삭제해야하는 경우 | ||
else if (root.right) { | ||
// successor.val로 값 대체 | ||
root.val = successor(root); | ||
// 재귀로 root.right의 값 제거 | ||
root.right = deleteNode(root.right, root.val); | ||
} | ||
// left subtree에서 삭제해야하는 경우 | ||
else { | ||
root.val = predecessor(root); | ||
root.left = deleteNode(root.left, root.val); | ||
} | ||
} | ||
return root; | ||
}; | ||
``` | ||
|
||
# 3. 결론 | ||
|
||
언젠가 간단하게 자료구조를 공부할 때, BST는 중위순회하면 이렇게 된다더라 정도의 기억만 남은상태에서 바라본 문제라 어떠한 실마리도 생각하지 못하였다. | ||
|
||
하지만 새로운 접근법을 알게 되었으니 기분.. 좋게 마무리 하는 문제이다. 복습이 꼭 필요한 문제다. | ||
|
||
## 참고 | ||
|
||
- [LeetCode - 450.Delete Node in a BST](https://leetcode.com/submissions/detail/1161990903/) |