算法通关 - 树&二叉树&二叉搜索树

树(Tree)

树是n (n>=0) 个节点的有限集。当n=0时,称为空树。在任意一个非空树下,有如下特点:

  • 有且仅有一个特定的称为根的节点
  • 当n>1时,其余节点可以分为m(m > 0) 个互不相交的有限集,每一个集合本身又是一个树,并成为根的子树
二叉树(Binary Tree)

二叉树是树的一种特殊形式,二叉树的每个节点最多有两个孩子节点。注意,这里是最多由两个,也可能只有一个,或者没有孩子节点。二叉树节点的两个孩子节点,一个被称为左孩子,一个被称为右孩子。

二叉树还有两种特殊的形式,一种是满二叉树,一种是完全二叉树。

一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树。简单说,满二叉树的每个分支都是满的。

对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树。完全二叉树条件没有满二叉树那么苛刻,满二叉树要求所有分支都是满的,而完全二叉树只需保证最后一个节点之前的节点都齐全即可。

//常用的二叉树的实现
public class TreeNode{
  public int val;
  public TreeNode left,right;
  public TreeNode(int val){
    this.val = val;
    this.left = null;
    this.right = null;
  } 
}
二叉搜索树(Binary Search Tree)
二叉查找树.png

二叉搜索树又称二叉排序树(sorted binary tree)、有序二叉树(ordered binary tree),是指一棵空树或者具有下列性质二叉树。

  • 左子树上所有节点的值均小于它的根节点的值
  • 右子树上所有节点的值均大于它的根节点的值
  • 左右子树也都是二叉查找树

简单理解:链表就是特殊化的树,树就是特殊化的图。链表其实是每一个节点有一个指针指向下一个节点,而树则可以理解为每个节点可以有多个指针指向其他节点。

各种数据结构的时间复杂度
各种数据结构时间复杂度.png
1.验证二叉搜索树(LeetCode - 98)

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

示例:
输入:
    2
   / \
  1   3
输出: true

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4 。

方法一:递归。将结点的值与上界和下界(如果有)比较。然后,对左子树和右子树递归进行该过程。

//定义二叉树节点
public class TreeNode {
  int val;
  TreeNode left;
  TreeNode right;

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

class Solution {
  public boolean helper(TreeNode node, Integer lower, Integer upper) {
    if (node == null) return true;

    int val = node.val;
    if (lower != null && val <= lower) return false;
    if (upper != null && val >= upper) return false;

    if (! helper(node.right, val, upper)) return false;
    if (! helper(node.left, lower, val)) return false;
    return true;
  }

  public boolean isValidBST(TreeNode root) {
    return helper(root, null, null);
  }
}
  • 时间复杂度 : O(N)。每个结点访问一次。
  • 空间复杂度 : O(N)。我们跟进了整棵树。​

方法二:迭代。使用栈,上面的递归法可以转化为迭代法。这里使用深度优先搜索,比广度优先搜索要快一些。

class Solution {
  LinkedList<TreeNode> stack = new LinkedList();
  LinkedList<Integer> uppers = new LinkedList(),
          lowers = new LinkedList();

  public void update(TreeNode root, Integer lower, Integer upper) {
    stack.add(root);
    lowers.add(lower);
    uppers.add(upper);
  }

  public boolean isValidBST(TreeNode root) {
    Integer lower = null, upper = null, val;
    update(root, lower, upper);

    while (!stack.isEmpty()) {
      root = stack.poll();
      lower = lowers.poll();
      upper = uppers.poll();

      if (root == null) continue;
      val = root.val;
      if (lower != null && val <= lower) return false;
      if (upper != null && val >= upper) return false;
      update(root.right, val, upper);
      update(root.left, lower, val);
    }
    return true;
  }
}
  • 时间复杂度 : O(N)。
  • 空间复杂度 : O(N)。

方法三:中序遍历

class Solution {
  public boolean isValidBST(TreeNode root) {
    Stack<TreeNode> stack = new Stack();
    double inorder = - Double.MAX_VALUE;

    while (!stack.isEmpty() || root != null) {
      while (root != null) {
        stack.push(root);
        root = root.left;
      }
      root = stack.pop();
      // If next element in inorder traversal
      // is smaller than the previous one
      // that's not BST.
      if (root.val <= inorder) return false;
      inorder = root.val;
      root = root.right;
    }
    return true;
  }
}
  • 时间复杂度 : 最坏情况下(树为二叉搜索树或破坏条件的元素是最右叶结点)为O(N)。
  • 空间复杂度 : O(N) ,用于存储 stack。​
2.二叉搜索树的最近公共祖先(LeetCode - 235)

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

方法一:递归。

1.从根节点开始遍历树

2.如果节点 p 和节点 q 都在右子树上,那么以右孩子为根节点继续 1 的操作

3.如果节点 p 和节点 q 都在左子树上,那么以左孩子为根节点继续 1 的操作

4.如果条件 2 和条件 3 都不成立,这就意味着我们已经找到节 p 和节点 q 的 LCA 了

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        //当前节点或者是根节点的值
        int parentVal = root.val;
        //节点p的值
        int pVal = p.val;
        //节点q的值
        int qVal = q.val;

        if (pVal > parentVal && qVal > parentVal) {
            // 如果p和q的值都比parentVal大
            return lowestCommonAncestor(root.right, p, q);
        } else if (pVal < parentVal && qVal < parentVal) {
            // 如果p和q的值都比parentVal小
            return lowestCommonAncestor(root.left, p, q);
        } else {
            //我们找到最近公共祖先了,就是当前root节点
            return root;
        }
    }
}


  • 时间复杂度:O(N)
    其中 N 为 BST 中节点的个数,在最坏的情况下我们可能需要访问 BST 中所有的节点。

  • 空间复杂度:O(N)

    所需开辟的额外空间主要是递归栈产生的,之所以是 N 是因为 BST 的高度为 N。

方法二:迭代。

我们用迭代的方式替代了递归来遍历整棵树。由于我们不需要回溯来找到 LCA 节点,所以完全可以不利用栈或者是递归的。实际上这个问题本身就是可以迭代的,我们只需要找到分割点就可以了。这个分割点就是能让节点 p 和节点 q 不能在同一颗子树上的那个节点,或者是节点 p 和节点 q 中的一个,这种情况下其中一个节点是另一个节点的父亲节点。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        int pVal = p.val;
        int qVal = q.val;

        TreeNode node = root;
        
        while (node != null) {
            int parentVal = node.val;
            if (pVal > parentVal && qVal > parentVal) {  
                node = node.right;
            } else if (pVal < parentVal && qVal < parentVal) {
                node = node.left;
            } else {
                return node;
            }
        }
        return null;
    }
}
  • 时间复杂度:O(N)
    其中 N 为 BST 中节点的个数,在最坏的情况下我们可能需要遍历 BST 中所有的节点。
  • 空间复杂度:O(1)
3.二叉树的最近公共祖先(LeetCode - 236)

该题目和上一题LeetCode - 235的唯一区别是只给出了是二叉树,并不是二叉搜索树。

方法:递归。首先我们判断root根节点是否等于p或q,如果等于其中一个那么root节点就是LCA。没有找到的话接下来分别遍历左子树和右子树, 看左子树和右子树中能否找到p或q。最后进行判断,如果遍历左子树的结果为空(也就是left== null),说明p和q都在右子树上,那么LCA就是右子树遍历的结果right。如果遍历右子树的结果为空(也就是right == null),说明p和q都在右子树上,那么LCA就是右子树遍历的结果left。如果遍历左右子树结果都不为空,说明p和q一个在左子树上一个在右子树上,那么要找的LCA就是root节点了

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

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