LeetCode 专题:二叉树和递归

LeetCode 第 104 题:二叉树的最大深度

提示:思路1:后序遍历:看完左右子树,才能计算自己;

思路2:使用 BFS。

LeetCode 第 111题:二叉树的最小深度

提示:思路1:BFS 就可以做,并且我觉得思路更直接一些。并且求二叉树的最小深度也是这个套路。

思路2:使用递归的话,注意只有左子树或者只有右子树的情况

Python 代码:

class Solution(object):
    def minDepth(self, root):
        if root is None:
            return 0
        depth = 0
        queue = [root]
        while queue:
            depth += 1
            size = len(queue)
            for _ in range(size):
                first = queue.pop(0)
                if first.left is None and first.right is None:
                    return depth
                if first.left:
                    queue.append(first.left)
                if first.right:
                    queue.append(first.right)

LeetCode 第 226 题:反转一棵二叉树

image-20181214103339221

LeetCode 第 100 题:判断两个二叉树是否一样

image-20181214103315065

LeetCode 第 101 题:检查一棵树是否是镜像对称的

提示:首先理解什么是镜面对称。两个结点的根结点的值必须相等,并且1、左边的左结点=右边的右结点;2、左边的右结点=右边的左结点。我觉得没有什么技巧,记住就可以了。使用下面这张图思考这个问题。

思路1:递归。

image-20181214103655847

思路2:双端队列。

image-20181214104909130

Python 代码:

class TreeNode(object):
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution(object):
    def isSymmetric(self, root):
        # 先写递归终止条件
        if root is None:
            return True

        # 其实应该用 from collections import deque
        deque = []

        deque.insert(0, root.left)
        deque.append(root.right)

        while deque:
            l_node = deque.pop(0)
            r_node = deque.pop()

            if l_node is None and r_node is None:
                continue
                
            if l_node is None or r_node is None:
                return False

            if l_node.val != r_node.val:
                return False
            deque.insert(0, l_node.right)
            deque.insert(0, l_node.left)
            deque.append(r_node.left)
            deque.append(r_node.right)
        return True

LeetCode 第 222 题:给出一个完全二叉树,求出该树的节点个数(典型递归问题)

提示:如果是满二叉树,结点的个数是有规律可循的。那么是不是满二叉树,可以通过子树的深度来判断。

Python 代码:

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None


class Solution:
    def countNodes(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        left_depth = self.__depth(root, True)
        right_depth = self.__depth(root, False)

        if left_depth == right_depth:
            # return 2 ** left_depth - 1
            return (1 << left_depth) - 1
        if left_depth > right_depth:
            return 1 + self.countNodes(root.left) + self.countNodes(root.right)

    def __depth(self, node, is_left):
        depth = 0
        while node:
            depth += 1
            node = node.left if is_left else node.right
        return depth

LeetCode 第 110 题:是否是平衡二叉树

一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

Python 代码:使用后序遍历

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

# 给定一个二叉树,判断它是否是高度平衡的二叉树。
# 本题中,


class Solution:
    def isBalanced(self, root):
        """
        :type root: TreeNode
        :rtype: bool
        """
        # 先写特殊情况,空树是平衡二叉树
        if root is None:
            return True
        return self.__helper(root) != -1

    def __helper(self, node):
        # 因为必须从底下到上面依次判断,所以使用后序遍历
        if node is None:
            return 0
        left = self.__helper(node.left)
        right = self.__helper(node.right)
        if left == -1 or right == -1 or abs(left - right) > 1:
            return -1
        # 重点,辅助函数的定义是,左右子树的高度中最大的那个
        return max(left, right) + 1

LeetCode 第 112 题:Path Sum

Python 代码:

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None


class Solution:
    def hasPathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: bool
        """

        if root is None:
            return False

        # 此时 root 不为空
        # 左边看一看,右边看一看
        # 【重点】一定要记得判断,左边子树和右边子树同时为空,说明走到底了
        if root.left is None and root.right is None and root.val == sum:
            return True

        left_has = self.hasPathSum(root.left, sum - root.val)
        right_has = self.hasPathSum(root.right, sum - root.val)
        return left_has or right_has

LeetCode 第 404 题:计算左边叶子结点数值之和

Python 代码:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:

    # 关键在于判断是否是左边叶子结点
    def sumOfLeftLeaves(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if root is None:
            return 0
        # 判断左边叶子结点是关键
        if root.left is not None and root.left.left is None and root.left.right is None:
            return root.left.val + self.sumOfLeftLeaves(root.right)
        return self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)

LeetCode 第 257 题:得到二叉树的所有路径。

给定一个二叉树,返回所有从根节点到叶子节点的路径。

传送门:https://leetcode.com/problems/binary-tree-paths/description/

分析:典型的回溯。下面的这个写法比较奇怪。

Java 代码:

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

public class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        // 这个节点是个根节点,想一想只有一个元素的情况
        if (root.left == null && root.right == null) {
            res.add(String.valueOf(root.val));
        }

        List<String> leftS = binaryTreePaths(root.left);
        for (int i = 0; i < leftS.size(); i++) {
            res.add(String.valueOf(root.val) + "->" + leftS.get(i));
        }
        List<String> rightS = binaryTreePaths(root.right);
        for (int i = 0; i < rightS.size(); i++) {
            res.add(String.valueOf(root.val) + "->" + rightS.get(i));
        }
        return res;
    }
}

心得:分析递归关系是这道题的难点。

LeetCode 第 113 题:给定一棵二叉树,返回所有从根节点到叶子节点的路径,并且要等于指定的和

Python 代码:

class Solution(object):
    def pathSum(self, root, sum):
        results = []
        self.__dfs([], root, sum, results)
        return results

    def __dfs(self, path, root, sum, results):
        if root is None:
            return
        if root.left is None and root.right is None and root.val == sum:
            result = []
            result.extend(path)
            result.append(root.val)
            results.append(result)
            return
        path.append(root.val)
        if root.left:
            self.__dfs(path, root.left, sum - root.val, results)
        if root.right:
            self.__dfs(path, root.right, sum - root.val, results)
        # 这一步很关键
        path.pop()

LeetCode 第 113 题:路径总和 II

要求:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

要特别注意弹出,即回溯的过程。

Java 代码:

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

public class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        // 根节点
        if (root.left == null && root.right == null) {
            if (root.val == sum) {
                List<Integer> temp1 = new ArrayList<>();
                temp1.add(root.val);
                result.add(temp1);
                return result;
            }
        }
        List<List<Integer>> leftLists = pathSum(root.left, sum - root.val);
        mergeOneAndList(root, leftLists, result);
        List<List<Integer>> rightLists = pathSum(root.right, sum - root.val);
        mergeOneAndList(root, rightLists, result);
        return result;
    }

    private void mergeOneAndList(TreeNode node, List<List<Integer>> listList, List<List<Integer>> result) {
        for (int i = 0; i < listList.size(); i++) {
            List<Integer> temp1 = new ArrayList<>();
            temp1.add(node.val);
            temp1.addAll(listList.get(i));
            result.add(temp1);
        }
    }
}

题后总结:使用递归的方法解决问题,很多时候,并不是让我们真正地去做这个问题,而是须要我们发现递归关系,寻找递归终止条件。历史上类似的经典问题有汉诺塔问题和八皇后问题。

但是,我自己觉得,我的解法,尤其是在 mergeOneAndList() 函数的部分稍显复杂。

下面给出一种简介的解法:这种解法显得更自然一些,遍历了从根节点到叶子节点的每一个节点,然后累加计算加到了多少,这是与老师的思路不同的一种思路。

public class Solution2 {

    private List<List<Integer>> result = new ArrayList<>();

    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        getSum(root, new ArrayList<Integer>(), 0, sum);
        return result;
    }

    private void getSum(TreeNode node, ArrayList<Integer> list, int current, int sum) {
        if (node == null) {
            return;
        }
        current += node.val;
        list.add(node.val);
        if (node.left == null && node.right == null) {
            if (current == sum) {
                result.add(list);
            } else {
                // 什么都不做
                // 在这里可以把不满足的节点都遍历出来
                return;
            }
        }
        if (node.left != null) {
            getSum(node.left, new ArrayList<Integer>(), current, sum);
        }
        if (node.right != null) {
            getSum(node.right, new ArrayList<Integer>(), current, sum);
        }
    }
}

LeetCode 第 129 题:给定一棵二叉树,每个节点只包含数字 0-9,从根节点到叶子节点的每条路径可以表示成一个数,求这些数的和

Python 代码:

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None


class Solution:
    def sumNumbers(self, root):
        res = []
        self.__dfs(root, 0, res)
        return sum(res)

    # Python 中对于基础的数据类型是值传递,即复制
    def __dfs(self, root, cum_sum, res):
        if root is None:
            return None
        if root.left is None and root.right is None:
            # 结算
            res.append(cum_sum * 10 + root.val)
            return
        self.__dfs(root.left, cum_sum * 10 + root.val, res)
        self.__dfs(root.right, cum_sum * 10 + root.val, res)

LeetCode 第 437 题:路径总和 III(重点,递归,二叉树典型处理思路)

提示:使用双重递归。

传送门:437. 路径总和 III

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例:

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

返回 3。和等于 8 的路径有:

1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11

Python 代码:参考资料:花花酱:https://zxi.mytechroad.com/blog/tree/leetcode-437-path-sum-iii/

image-20190213071747921

Python 代码:

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def pathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: int
        """
        from collections import defaultdict
        memo = defaultdict(int)
        # 记忆化递归,memo 表示缓存
        memo[0] = 1  # 表示 cur - sum = 0, return 1
        self.res = 0

        def helper(root, curSum):
            if root is None:
                return
            curSum += root.val
            self.res += memo[curSum - sum]
            memo[curSum] += 1

            helper(root.left, curSum)
            helper(root.right, curSum)
            memo[curSum] -= 1

        helper(root, 0)
        return self.res
        

Java 代码:

public class Solution {
    public int pathSum(TreeNode root, int sum) {
        if(root == null)
            return 0;
        return dfs(root, sum)+pathSum(root.left, sum)+pathSum(root.right, sum);
    }

    private int dfs(TreeNode root, int sum){
        int res = 0;
        if(root == null)
            return res;
        if(sum == root.val)
            res++;
        res+=dfs(root.left,sum - root.val);
        res+=dfs(root.right,sum - root.val);
        return res;
    }
}

LeetCode 第 235 题:二叉搜索树的最近公共祖先

LeetCode 第 98 题:是否是二分搜索树

提示:思路1:中序遍历一遍就可以了;思路2:模拟系统栈。

Java 代码:

class Solution {
    double last = -Double.MAX_VALUE;
    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (isValidBST(root.left)) {
            if (last < root.val) {
                last = root.val;
                return isValidBST(root.right);
            }
        }
        return false;
    }
}

LeetCode 第 450 题 :删除二叉搜索树中的节点(一定要会!)

LeetCode 第 108 题:将有序数组转换为二叉搜索树

LeetCode 第 230 题:二叉搜索树中第 K 小的元素

思路1:递归。

思路2:用模拟系统栈。

LeetCode 第 236 题:二叉树的最近公共祖先

LeetCode 第 297 题:二叉树的序列化与反序列化

传送门:二叉树的序列化与反序列化。同《剑指 Offer 》第 37 题。

使用前序遍历和中序遍历的结果还原一棵二叉树。

LeetCode 第 703 题:数据流中的第 k 大元素

下面的问题都来自 BST(二分搜索树)。

LeetCode 第 235 题:二分搜索树的最近公共祖先

LeetCode 第 450 题:删除一个二分搜索树的结点(必会,不难,多写几遍)

LeetCode 第 108 题:将有序数组转换为二叉搜索树

LeetCode 第 203 题:二叉搜索树中第 K 小的元素

LeetCode 第 236 题:二叉树的最近公共祖先

LeetCode 第 96 题:

要求:给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

分析:使用动态规划。

image-20190114163626332

Python 代码:

class Solution:
    def numTrees(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 0 or n == 1:
            return 1
        dp = [0] * (n + 1)
        dp[0] = 1
        dp[1] = 1
        for i in range(2, n + 1):
            for j in range(i):
                dp[i] += dp[j] * dp[i - j - 1]
        return dp[n]

Java 代码:

public class Solution {

    public int numTrees(int n) {
        int[] dp = new int[n + 1];
        // 想清楚这个值很关键
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            // 这里 j 表示左子树的元素个数,最小是 0 ,最大是 i-1
            // 左边子树 + 右边子树 = i - 1
            // i - j - 1 表示的是右边子树元素个数
            for (int j = 0; j < i; j++) {
                // 使用 * 是因为乘法计数原理
                dp[i] += dp[j] * dp[i - j - 1];
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int n = 3;
        int numTrees = solution.numTrees(n);
        System.out.println(numTrees);
    }
}

LeetCode 第 95 题:不同的二叉搜索树 II

要求:给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树

示例:

输入: 3
输出:
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

分治算法:

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None


class Solution:
    def generateTrees(self, n):
        """
        :type n: int
        :rtype: List[TreeNode]
        """

        res = []
        if n <= 0:
            return res
        return self.helper(1, n)

    def helper(self, left, right):
        res = []
        if left > right:
            # 说明不构成区间,应该返回空结点
            res.append(None)
            return res
        elif left == right:
            res.append(TreeNode(left))
            return res
        else:
            for i in range(left, right + 1):
                left_sub_tree = self.helper(left, i - 1)
                right_sub_tree = self.helper(i + 1, right)
                for l in left_sub_tree:
                    for r in right_sub_tree:
                        root = TreeNode(i)
                        root.left = l
                        root.right = r
                        res.append(root)
        return res

动态规划:这个解法几乎和 96 题是一模一样的。

注意:要写一个辅助函数,拷贝一个二叉树,有一定的偏移。

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None


class Solution:
    def generateTrees(self, n):
        """
        :type n: int
        :rtype: List[TreeNode]
        """
        if n <= 0:
            return []
        res = [None] * (n + 1)
        res[0] = [None]
        for i in range(1, n + 1):
            # 初始化一个列表对象
            res[i] = []
            for j in range(i):
                for left in res[j]:
                    for right in res[i - j - 1]:
                        # 注意:这里是 j + 1 ,表示根结点,画个图就很清楚了
                        root = TreeNode(j + 1)
                        root.left = left
                        # 每个结点都有固定偏移的拷贝
                        root.right = self.__shift_clone(right, j + 1)
                        res[i].append(root)
        return res[n]

    def __shift_clone(self, root, k):
        if root is None:
            return root
        cur_node = TreeNode(root.val + k)
        cur_node.left = self.__shift_clone(root.left, k)
        cur_node.right = self.__shift_clone(root.right, k)
        return cur_node

LeetCode 第 94 题:二叉树的中序遍历

传送门:https://leetcode.com/problems/binary-tree-inorder-traversal/description/

递归写法:

Java 代码实现:

public class Solution2 {

    private List<Integer> result = new ArrayList<>();

    public List<Integer> inorderTraversal(TreeNode root) {

        inorder(root);
        return result;
    }

    private void inorder(TreeNode root) {
        if (root != null) {
            inorder(root.left);
            result.add(root.val);
            inorder(root.right);
        }
    }
}

非递归写法

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

enum UseType {
    RECURSION,
    ADD
}

class Command {
    UseType useType;
    TreeNode treeNode;

    Command(UseType useType, TreeNode treeNode) {
        this.useType = useType;
        this.treeNode = treeNode;
    }
}

/**
 * 什么是中序遍历,先递归遍历左子节点
 * 在处理自己
 * 然后再递归遍历右子节点
 */
public class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        Stack<Command> stack = new Stack<>();
        stack.push(new Command(UseType.RECURSION, root));

        while (!stack.isEmpty()) {
            Command command = stack.pop();
            if (UseType.ADD == command.useType) {
                result.add(command.treeNode.val);
            } else {
                assert UseType.RECURSION == command.useType;
                if (command.treeNode.right != null) {
                    stack.push(new Command(UseType.RECURSION, command.treeNode.right));
                }
                stack.push(new Command(UseType.ADD, command.treeNode));
                if (command.treeNode.left != null) {
                    stack.push(new Command(UseType.RECURSION, command.treeNode.left));
                }
            }

        }
        return result;
    }
}

(本节完)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275

推荐阅读更多精彩内容