Skip to content

Comments

[oh-chaeyeon] 25.02.06#40

Merged
JooKangsan merged 49 commits intomainfrom
ohchaeyeon
Feb 13, 2025
Merged

[oh-chaeyeon] 25.02.06#40
JooKangsan merged 49 commits intomainfrom
ohchaeyeon

Conversation

@oh-chaeyeon
Copy link
Collaborator

@oh-chaeyeon oh-chaeyeon commented Feb 10, 2025

[트리]

🎄트리(Tree)란?

  • 노드(Node)와 간선(Edge)로 이루어진 자료구조.
  • 노드(Node) : 데이터를 저장하는 단위
  • 간선(Edge) : 노드 간의 연결
  • 그래프의 한 종류. 순환(Cycle)이 없는 연결 그래프로 부모-자식 관계만 있고, 한 방향으로만 연결되어 있음.

트리의 기본 용어

  • 루트 노드(Root Node): 트리의 최상위 노드
  • 부모 노드(Parent Node): 다른 노드를 가리키는 노드
  • 자식 노드(Child Node): 부모 노드에 의해 연결된 노드
  • 리프 노드(Leaf Node): 자식이 없는 노드
  • 깊이(Depth): 루트 노드에서 특정 노드까지의 거리
  • 높이(Height): 특정 노드에서 가장 깊은 노드까지의 거리

트리의 종류

  1. 이진 트리 : 모든 노드가 최대 두 개의 자식 노드를 가질 수 있는 트리
  2. 완전 이진 트리 : 왼쪽부터 차례대로 채워진 트리
  3. 이진 탐색 트리 : 왼쪽 자식 < 부모 < 오른쪽 자식의 규칙을 따르는 트리
  4. 균형 이진 트리 : 높이 균형을 유지하여 검색 속도를 빠르게 함

⭐가장 유명한 이진 트리의 순회 방법

방법1. DFS (Depth-First Search, 깊이 우선 탐색)
-> 한쪽 방향으로 깊이 들어가면서 탐색하는 방식

  • 전위 순회 (Preorder): Root → Left → Right
  • 중위 순회 (Inorder): Left → Root → Right
  • 후위 순회 (Postorder): Left → Right → Root

방법2. BFS (Breadth-First Search, 너비 우선 탐색)
-> 루트에서 가까운 노드부터 탐색하는 방식

🌟그리고 문제 풀면서 항상 나오는 개념이라고 생각하는...🌟

재귀란?

  • 자기 자신을 호출하는 함수
  • 어떤 함수가 자기 자신을 반복해서 호출하면서 문제를 해결하는 방식

💡재귀 함수의 필수 요소

  1. 기저 조건(Base Case): 재귀 호출을 멈추는 조건
  2. 재귀 호출(Recursive Call): 자기 자신을 호출하는 부분

-> 트리문제에서 재귀적 방법으로 풀이하는 이유는!
트리의 구조가 각 노드는 또 다른 작은 트리로 구성되기 때문에, 자기 자신을 호출하는 방식(재귀)이 자연스럽게 적용될 수 있음.

해시맵이란?

  • 키(Key)와 값(Value)의 쌍으로 데이터를 저장하는 자료구조
  • Map 객체나 Object를 사용해서 해시맵을 구현할 수 있음.
  • 키(Key)를 이용해 데이터를 빠르게 찾을 수 있음. 중복 키 허용안함.

-> 트리 문제에서 해시맵을 사용하는 이유는!
트리 문제에서는 특정 값을 빠르게 찾거나 저장해야 하는 경우가 많은데, 이때 해시맵을 사용하면 **O(1)**로 해결할 수 있음.

💡해시맵이 유용한 경우

  1. 노드 방문 여부 체크 (ex: 중복 검사)
  2. 값을 빠르게 저장하고 조회 (ex: 부모-자식 관계 저장)
  3. 트리에서 특정 속성을 저장 (ex: 깊이별 노드 저장)

📌 푼 문제


📝 간단한 풀이 과정

104. Maximum Depth of Binary Tree

  • 이진트리에서 최대깊이를 구하는 문제
  • 재귀를 이용하여 왼쪽과 오른쪽 서브트리의 깊이를 구한 뒤, 더 큰 깊이에 1을 추가하여 최대 깊이를 구함.
var maxDepth = function(root) {
    if (root === null){ // 노드가 없으면 깊이는 0
        return 0; 
    }

    let leftDepth = maxDepth(root.left);
    let rightDepth = maxDepth(root.right);

    return Math.max(leftDepth, rightDepth) + 1;
};

94. Binary Tree Inorder Traversal

  • 왼쪽 서브트리를 방문 → 현재 노드(루트)를 방문 → 오른쪽 서브트리를 방문한 결과를 반환하는 문제
  • 재귀를 이용해 왼쪽 → 루트 → 오른쪽" 순서로 탐색하면서 결과 배열에값을 추가함.
var inorderTraversal = function(root) {
    const result = [];
    function traverse(node) {
        if (!node) return;
        traverse(node.left);  //왼쪽 서브트리 탐색
        result.push(node.val);  //현재 노드 방문
        traverse(node.right);  //오른쪽 서브트리 탐색
    }
    traverse(root);
    return result;
};

100. Same Tree

  • 두 개의 이진 트리 p와 q가 완전히 동일한 트리인지 확인하는 문제
  • 두 트리가 같다는 의미 -> 구조적으로 동일(트리의 모양이 같아야 함) and 각 노드의 값이 동일해야 함.
  • 두 개의 트리를 재귀적으로 순회하면서 구조와 값을 비교하면서 동일하면 true를 반환.
var isSameTree = function(p, q) {
    if (p === null && q === null) {  //둘 다 null이면 같은 트리임으로 true 반환
        return true;
    }
    
    if (p === null || q === null) {
        return false;
    }
    
    if (p.val !== q.val) {
        return false;
    }
    
    return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
};

226. Invert Binary Tree

  • 이진 트리의 좌우 자식을 서로 바꾼(Invert) 트리를 반환하는 문제
  • 트리를 반전한다는 의미 : 각 노드의 왼쪽과 오른쪽 자식을 바꿔주는 것!
var invertTree = function(root) {
    if(root === null){
        return null;
    }

    let temp = root.left;
    root.left = root.right;
    root.right = temp;

    invertTree(root.left);
    invertTree(root.right);

    return root;
};

102. Binary Tree Level Order Traversal

  • 이진 트리의 레벨 순회 결과를 반화하는 문제
  • 레벨 순회란? 트리의 각 노드를 층별로 왼쪽에서 오른쪽으로 순서대로 방문하는 방식
var levelOrder = function(root) {
    const result = [];
    
    function traverse(node, level) {
        if (!node) return; //노드가 없으면 종료
        if (!result[level]) result[level] = [];  //해당 레벨이 없으면 빈 배열 생성
        result[level].push(node.val);  //해당 레벨의 배열에 노드 값 추가
        traverse(node.left, level + 1); 
        traverse(node.right, level + 1); 
    }
    
    traverse(root, 0);
    return result;
};

98. Validate Binary Search Tree

  • 주어진 이진 트리가 이진 검색 트리인지 판별하는 문제
  • 이진 검색 트리(BST) 조건!!!
  1. 왼쪽 서브트리의 모든 노드는 현재 노드보다 작은 값을 가져야 함.
  2. 오른쪽 서브트리의 모든 노드는 현재 노드보다 큰 값을 가져야 함.
  3. 모든 서브트리 또한 BST 조건을 만족해야 함.
var isValidBST = function(root) {
    function validate(node, lower, upper) {
        if (!node) return true; 

        const val = node.val;
        if (val <= lower || val >= upper) return false; 

        return validate(node.left, lower, val) && validate(node.right, val, upper);
    }

    return validate(root, -Infinity, Infinity); 
};

105. Construct Binary Tree from Preorder and Inorder Traversal

  • 전위 순회(Preorder)와 중위 순회(Inorder) 결과가 주어질 때, 이진 트리를 복원하는 문제

트리 순회 방법

  1. 전위 순회 (Preorder: Root → Left → Right) : 루트를 먼저 방문한 후 왼쪽 서브트리를 탐색, 이후 오른쪽 서브트리 탐색.
  2. 중위 순회 (Inorder: Left → Root → Right) : 왼쪽 서브트리를 먼저 탐색한 후 루트를 방문, 이후 오른쪽 서브트리 탐색.
var buildTree = function(preorder, inorder) {
   // inorderMap을 이용해 값의 위치를 저장
    const inorderMap = new Map();
    inorder.forEach((val, idx) => inorderMap.set(val, idx)); 

    function build(preStart, preEnd, inStart, inEnd) {
        if (preStart > preEnd || inStart > inEnd) return null;

        const rootVal = preorder[preStart];
        const root = new TreeNode(rootVal);
        const inRootIndex = inorderMap.get(rootVal);
        const leftSize = inRootIndex - inStart; 

        root.left = build(preStart + 1, preStart + leftSize, inStart, inRootIndex - 1);
        root.right = build(preStart + leftSize + 1, preEnd, inRootIndex + 1, inEnd);

        return root;
    }

    return build(0, preorder.length - 1, 0, inorder.length - 1);
};

이진 변환 반복하기

  • 문자열에서 0을 제거한 후 길이를 2진수로 변환하는 과정을 반복하여 "1"이 될 때까지 변환 횟수와 제거된 0의 개수를 계산.
function solution(s) {
    let count = 0;  //변환 횟수
    let removed = 0; //제거된 0의 개수

    while (s !== "1") {
        const originalLength = s.length;  // 원래 문자열 길이
        const newStr = s.split('').filter(char => char === '1').join(''); //0을 제거한 문자열
        removed += originalLength - newStr.length; //제거된 0의 개수 추가
        s = newStr.length.toString(2); 
        count++; 
    }

    return [count, removed];
}

124. Binary Tree Maximum Path Sum

  • 이진 트리에서 최대 경로 합을 구하는 문제
  • 깊이 우선 탐색을 사용함.
  • 깊이 우선 탐색(DFS)란? 트리의 각 노드를 깊게 탐색하면서 방문하고, 더 이상 내려갈 노드가 없으면 이전 노드로 돌아가 다시 탐색을 이어가는 방식
var maxPathSum = function(root) {
    let maxSum = -Infinity; //최대 경로 합을 저장할 변수

    function dfs(node) {
        if (!node) return 0; 

        const left = Math.max(dfs(node.left), 0); 
        const right = Math.max(dfs(node.right), 0); 

        const currentSum = node.val + left + right;

        maxSum = Math.max(maxSum, currentSum);

        return node.val + Math.max(left, right);
    }

    dfs(root);
    return maxSum;
};

트리는 항상 봐도 어렵네요,, 다른 문제들보다

@oh-chaeyeon oh-chaeyeon self-assigned this Feb 10, 2025
Copy link
Collaborator

@bona1122 bona1122 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정리해 주신 내용이랑 코드들 잘 봤습니다. 이진검색트리 검증 문제 풀어주신 코드 보면서 직계자손뿐 아닌 왼쪽 '서브트리' < 루트 < 오른쪽 '서브트리' 대해 한번 더 리마인드 하고 갑니다. 그리고 (숫자).toString(n) : n진수 문자열로 변환도 배우고 갑니다! 여담으로 매번 느끼지만 재귀로 짜여진 코드는 한번에 이해하기 참 어렵네요 ㅜㅜ... 한 주 동안 수고하셨습니다!

@JooKangsan JooKangsan merged commit eb4df84 into main Feb 13, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants