LeetCode刷题笔记9.9-9.15

LeetCode刷题笔记9.9-9.15

二叉树

主要学两种遍历方式:层序遍历、递归遍历

1)层序遍历BFS

基本思想:逐层遍历元素,可以借助队列,先进先出,队首出元素的同时进该元素的左右节点(这也是最简单的实现方式)

队列Q:1 -> 出1 进2,3(2,3)-> 出2 进4(3,4)-> 出3 进5,6(4,5,6)-> 出4(5,6)-> 出5(6)-> 出6(空)
队列进出元素的操作在遇到队列为空时停止

void levelOrderTraverse(TreeNode root) {
    if (root == null) {
        return;
    }
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);
    while (!q.isEmpty()) {
        TreeNode cur = q.poll();
        // 访问 cur 节点
        System.out.println(cur.val);

        // 把 cur 的左右子节点加入队列
        if (cur.left != null) {
            q.offer(cur.left);
        }
        if (cur.right != null) {
            q.offer(cur.right);
        }
    }
}

进阶思想:在层次遍历过程中记录树的深度
记录深度的时机:

队列Q:1 -> 出1 进2,3(2,3)-> 出2 进4(3,4)-> 出3 进5,6(4,5,6)-> 出4(5,6)-> 出5(6)-> 出6(空)

当前层的所有节点的孩子节点全部列入队列时,此时一定是当前层的最后一个节点刚出队列,比如以上变换过程中的出3后,当前第2层(从1层算起)遍历结束,第3层开始遍历
修改基础的遍历代码:

void levelOrderTraverse(TreeNode root) {
    if (root == null) {
        return;
    }
    int depth = 0;
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);
    while (!q.isEmpty()) {

        int depthSize = q.size();
        for(int i = 0; i < depthSize; i++){
            TreeNode cur = q.poll();
            // 访问 cur 节点
            System.out.println(cur.val);

            // 把 cur 的左右子节点加入队列
            if (cur.left != null) {
                q.offer(cur.left);
            }
            if (cur.right != null) {
                q.offer(cur.right);
            }
        }

        depth++;
    }
}

【二叉树的层序遍历可以用到求与二叉树最短深度/到叶子节点的最短路径相关的题目中,深度遍历必须要遍历完所有树枝的深度,作比较后才可得到】

2)递归遍历DFS

理解递归遍历与迭代循环遍历的区别和联系

// 迭代遍历数组
void traverse(int[] arr) {
    for (int i = 0; i < arr.length; i++) {

    }
}

// 递归遍历数组
void traverse(int[] arr, int i) {
    if (i == arr.length) {
        return;
    }
    // 前序遍历要做的操作
    // 这里是每层递归刚进来的位置 适用于正序遍历
    traverse(arr, i + 1);
    // 后序遍历要做的操作
    // 这里是每层递归快要结束的位置 适用于反序遍历
}

// 迭代遍历单链表
void traverse(ListNode head) {
    for (ListNode p = head; p != null; p = p.next) {

    }
}

// 递归遍历单链表
void traverse(ListNode head) {
    if (head == null) {
        return;
    }
    // 前序位置
    traverse(head.next);
    // 后序位置
}

再来看二叉树/多叉树的递归遍历

void traverse(TreeNode root){
    if(root == null) return;
    // 这里是每层递归刚刚进入的位置,是前序的操作
    traverse(root.lchild);
    // 这里是递归左子树结束的位置,是中序的操作
    traverse(root.rchild);    
    // 这里是每层递归将要结束的位置,是后序的操作
}
class Node{
    int val;
    List<Node> children;
}
void traverse(Node root) {
    if (root == null) {
        return;
    }
    // 前序位置
    for (Node child : root.children) {
        // 依次遍历所有孩子节点来递归
        traverse(child);
    }
    // 后序位置
}

由此推导出二叉树的遍历实现规律:
前中后序是遍历二叉树过程中处理每一个节点的三个特殊时间点
不仅仅是三个顺序不同的 List:
前序位置的代码在刚刚进入当前二叉树节点的时候执行;
后序位置的代码在将要离开一个二叉树节点的时候执行;
中序位置的代码在一个二叉树节点左子树都遍历完,即将开始遍历右子树的时候执行。

二叉树的所有问题,就是让你在前中后序位置注入巧妙的代码逻辑,去达到自己的目的,你只需要单独思考每一个节点应该做什么,其他的不用你管,抛给二叉树遍历框架,递归会在所有节点上做相同的操作。

posted @ 2024-11-14 14:22  CandyWang-  阅读(3)  评论(0编辑  收藏  举报