算法小抄学习笔记 — 1.二叉树递归思想训练(一)

1 二叉树递归基础

就是常见的先序、中序和后序遍历框架,如下:

/* 二叉树遍历框架 */
void traverse(TreeNode root) {
    // 前序遍历
    traverse(root.left)
    // 中序遍历
    traverse(root.right)
    // 后序遍历
}

写递归算法的关键是要明确函数的「定义」是什么,然后相信这个定义,利用这个定义推导最终结果,绝不要试图跳入递归。



2 快排,归并排序与二叉树遍历的关系

2.1 快排就是二叉树的先序遍历

快排首先找到一个分界点,所有小于分界点的值在其左边,所有大于分界点的值在其右边,然后再在左半区间和右半区间进行快排,直到区间不可划分为止。

其实就是先序遍历的思想,代码框架如下:

void sort(int[] nums, int lo, int hi) {
    /****** 前序遍历位置 ******/
    // 通过交换元素构建分界点 p
    int p = partition(nums, lo, hi);
    /************************/

    sort(nums, lo, p - 1);
    sort(nums, p + 1, hi);
}

2.2 归并排序就是二叉树的后序遍历

归并排序最初在相邻两个元素排序,然后合并。再将合并的较大区间作为一个整体,在这样相邻两个整体间排序,再合并。依次到全部合并完为止。

如果将两个元素或区间看成左子树和右子树,其实就是后序遍历,代码框架如下:

void sort(int[] nums, int lo, int hi) {
    int mid = (lo + hi) / 2;
    sort(nums, lo, mid);
    sort(nums, mid + 1, hi);

    /****** 后序遍历位置 ******/
    // 合并两个排好序的子数组
    merge(nums, lo, mid, hi);
    /************************/
}



3 算法实践

3.1 226. 翻转二叉树

3.1.1 题目

翻转一棵二叉树。

示例:

输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

3.1.2 思路

使用递归的思想,假定这个函数的功能已经是翻转以root为结点的方法。先调用函数翻转左子树,然后翻转右子树,再交换根节点下的左右子树即可。

3.1.3 代码

class Solution {
    public TreeNode invertTree(TreeNode root) {
        // base case
        if (root == null) { return null; }
        
        // 后序遍历
        TreeNode leftNode = invertTree(root.left);
        TreeNode rightNode = invertTree(root.right);
        
        // 交换左右子树
        root.left = rightNode;
        root.right = leftNode;

        return root;
    }
}

3.2 116. 填充每个节点的下一个右侧节点指针

3.2.1 题目

给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL

初始状态下,所有 next 指针都被设置为 NULL

3.2.2 思路

跟上一题基本思路一致,利用后序遍历处理子树连接问题。但是本题还是有些不一样,除了左子树的next连接到右子树外,还需要左子树的最右侧所有结点的next都要连接到右子树最左侧的所有右结点。

3.2.3 代码

class Solution {
    public Node connect(Node root) {
        // base case
        if (root == null) { return null; }
        if (root.left == null) { return root; }

        // 后序遍历
        Node leftNode = connect(root.left);
        Node rightNode = connect(root.right);
        
        // 连接
        while (leftNode != null) {
            leftNode.next = rightNode;
            leftNode = leftNode.right;
            rightNode = rightNode.left;
        }
        
        return root;
    }
}

3.3 114. 二叉树展开为链表

3.3.1 题目

给定一个二叉树,原地将它展开为一个单链表。

例如,给定二叉树

    1
   / \
  2   5
 / \   \
3   4   6

将其展开为:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

3.3.2 思路

使用递归思想(没有实现原地),分别将左右都展开成一个单链表,将左子树接到根节点的右子树上,右子树接到原先左子树的末尾。

3.3.3 代码

class Solution {
    public void flatten(TreeNode root) {
        // base case
        if (root == null) { return; }

        // 后序遍历
        flatten(root.left);
        flatten(root.right);

        /**** 后序遍历逻辑位置 ****/
        // 1. 左右子树已被拉成一条直线
        TreeNode leftNode = root.left;
        TreeNode rightNode = root.right;

        // 2. 将左子树作为右子树
        root.left = null;
        root.right = leftNode;

        // 3. 将原先的右子树接到当前右子树的末端
        TreeNode p = root;
        while (p.right != null) {
            p = p.right;
        }
        p.right = rightNode;
    }
}



4 参考

  • 手把手带你刷二叉树(第一期)

你可能感兴趣的:(leetcode,二叉树,递归算法)