morningli

有读者反馈,现在谁不是为了找工作才学的数据结构,确实很有道理,是我肤浅了。所以为了满足大家的需求,这里总结下LeetCode里的数据结构。对于我们这种职场老人来说,刷LeetCode会遇到个很尴尬的问题,就是每道题看起来都很熟悉,都觉得是十拿九稳了,但是真要你写出来,又很容易卡壳。如果人家让你写个反转链表的题目都能卡壳,那场面就会相当尴尬了。起初我还是对刷题比较抗拒的,感觉就跟学习交通法规一样,你花个半天时间就能背下来,就能考个九十多分,靠这个来面试实在是太low了。现在也释然了,一方面是现在人才太多了,企业已经没法通过面试来筛选人才,所以选择算法来做一层保底的筛选,如果你不会,就算你有多牛,也只能归类到垃圾的队伍了,所以这事得顺应时势,适者生存;另一方面比如字节这种公司确实对算法需求比较强,人家考核算法也是实际需要,也没必要觉得他是在有意刁难。

LeetCode题型

LeetCode应该怎么刷呢,首先很重要的一点是要多花时间,每天刷一两道题,保持好做题的感觉。这玩意熟能生巧,经常练习有助于治疗老年痴呆。另外我在网络收集了一些网友刷LeetCode的经验,对于初刷LeetCode的同学比较有用:

  • 按分类刷;每个分类从 Easy 到 Medium 顺序刷;
  • 优先刷 树、链表、二分查找、DFS、BFS 等面试常考类型;
  • 优先刷题号靠前的题目;
  • 优先刷点赞较多的题目;

本文主要是讲LeetCode中的数据结构以及考点,方便大家加深认识。这次我们首先讲的是LeetCode里的树。为什么先讲树呢,因为树是我们用的最多,实用性最强,也最容易在面试中被问到的一种数据结构,小到我们编程语言里的map,大到mysql的索引,都离不开树这个数据结构。可以这样说,你把树搞明白了,至少能应付一半的面试了。

在LeetCode的标签分类题库中,和树有关的标签有:树(227)、二叉树(198)、二叉搜索树(54)、字典树(49)、线段树(28)、树状数组(20)、最小生成树(5)。这些题中,二叉树又是树中的重点,可以归纳为:

 

上图主要是归纳了一些LeetCode常见题型和概念,方便大家记忆和查漏补缺。其中打星的是其中比较难的部分,不过这东西见仁见智,也许有些人也会觉得很简单。

下面帮大家回忆一下关于二叉树的基础概念。

要理解什么是二叉树,首先需要理解什么是树( tree )。可以使用递归的方式定义树:

一棵树是一些节点( node )的集合。这个集合可以是空集,说不是空集,则树由称作( root )的节点 r 以及0个或多个非空的(子)树T1, T2, ..., Tk组成,这些子树中每一棵都被来自根r的一条有向的( edge )所连接。

每一棵子树的根叫作根 r 的儿子 (child), 而 r 是每一棵子树的根的父亲 ( parent )。下图展示了用递归定义的典型的树:

在下面的树中,节点A是根。节点F有一个父亲A并且有儿子K、L和M。每一个节点可以有任意多个儿子,也可能是是零个儿子。没有儿子的节点称为树叶( leaf ),图中的树叶是B、C、H、I、P、Q、K、L、M和N。具有相同父亲的节点为兄弟 ( siblings )。因此,K、L和M都是兄弟。用类似的方法可以定义祖父( grandparent )和孙子( grandchild )关系。

 

从节点 n1 到 nk路径( path )定义为节点n1, n2, ..., nk的一个序列,是的对于 1 <= i < k 的节点ni是ni+1的父亲,这条路径的( length ) 是该路径上的边的条数,即 k-1。从每一个节点到他自己有一条长为0的路径。注意,在一棵树中从根到每个节点恰好存在一条路径。

对于任意节点ni,n深度( depth ) 为从根到 ni 的唯一路径的长。因此,根的深度为 0。节点 ni 的高( height )是从ni到一片树叶的最长路径的长。因此所有的树叶的高都是0。一棵树的高度( height of a tree )等于它的根的高。对于图中的树,E的深度为1而高为2;F的深度为1而高也是1;该树的高为3。一棵树的深度( depth of a tree )等于其最深的树叶的深度,该深度总是等于这棵树的高。

如果存在从n1到n2的一条路径,那么n1是n2的一位祖先( ancestor ),而n2是n1的一个后裔( descendant )。如果n1 != n2,那么n1是n2真祖先(proper ancestor),而n2是n1真后裔(proper descendant)。

二叉树

二叉树(Binary tree)是树形结构的一个重要类型,是AVL树,红黑树,二叉堆,大顶堆,小顶堆,多叉树的基础。那么什么是二叉树?下面给出二叉树的递归定义:

二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。

以上图为例,其中 6 是二叉树的根节点,A和B分别是根节点 6 的左子树和右子树;直线相连的节点分别是父节点和子节点,比如 6 是 2 和 8 的父节点,2 和 8 是 6 的子节点;没有子节点的节点叫叶子节点,比如 0、3、5、7、9 、10都是叶子节点。 

相关术语

节点(node):包含一个数据元素及若干指向子树分支的信息。上图中的每个圆圈都表示一个节点。

度(degree):一个节点拥有子树的数目称为节点的度 ,叶子的度一定是0。上图中的6、2、8、4的度为2,9的度为1,0、3、5、7、9 、10的度为0。

叶子节点(leaf node:也称为外部节点(external node  outer node终端节点(terminal node,指没有子树的节点或者度为零的节点。上图中的0、3、5、7、9 、10都是叶子节点。

内部节点(internal node 或 inner node:也称为非终端节点分支节点branch node),度不为零的节点称为非终端节点。上图中的6、2、8、4、9都是内部节点。

树的度(degree of tree):树中所有节点的度的最大值。上面的树的度是2,二叉树的度不会超过2。

层(level:节点的层是沿着它和根节点之间的唯一路径的边数。LeetCode中的层是从0开始的,比如上图中的6是第0层,2是第一层。

距离(Distance):沿两个节点之间最短路径的边数。

有序树(Ordered tree):为每个顶点的子节点指定排序的有根树。

森林(Forest):由m(m≥0)棵互不相交的树构成一片森林。如果把一棵非空的树的根节点删除,则该树就变成了一片森林,森林中的树由原来根节点的各棵子树构成。

满二叉树(full binary tree):每个节点都有 0 或 2 个子节点的二叉树。

完全二叉树(complete binary tree):除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2个节点。下图所示是一棵完全二叉树:

完美二叉树(perfect binary tree):所有内部节点都有两个孩子并且所有叶子都具有相同深度或相同级别。一棵完美的树总是完全二叉树,但一棵完全二叉树不一定是完美的。

基本操作

递归

针对二叉树的问题的解决中,递归是一种十分常用的编程方式,有必要说明一下。

定义

数学和计算机科学中,递归指由一种(或多种)简单的基本情况定义的一类对象或方法,并规定其他所有情况都能被还原为其基本情况。

例如,下列为某人祖先的递归定义:

  • 某人的双亲是他的祖先(基本情况)。
  • 某人祖先的双亲同样是某人的祖先(递归步骤)。

斐波那契数列是典型的递归案例:

  • {\displaystyle F_{0}=0}F_{0}=0(初始值)
  • {\displaystyle F_{1}=1}F_{1}=1(初始值)
  • 对所有大于1的整数n:{\displaystyle F_{n}=F_{n-1}+F_{n-2}}F_{n}=F_{n-1}+F_{n-2}(递归定义)

一种便于理解的心理模型,是认为递归定义对对象的定义是按照“先前定义的”同类对象来定义的。例如:你怎样才能移动100个箱子?答案:你首先移动一个箱子,并记下它移动到的位置,然后再去解决较小的问题:你怎样才能移动99个箱子?最终,你的问题将变为怎样移动一个箱子,而这时你已经知道该怎么做的。

C++的递归

直接或间接调用自己的函数称为递归函数(recursion function)。一个简单的定义函数的例子是阶乘的计算。数n的阶乘是从1到n的乘积。例如5的阶乘就是120。

1 * 2 * 3 * 4 * 5 = 120

解决这个问题的自然方法就是递归:

int factorial(int val)
{
    if (val > 1)
        return factorial(val-1)*val;
    return 1;    
}

递归函数必须定义一个终止条件;否则就会永远递归下去,这意味着函数会一直调用自身知道程序栈耗尽。有时候这种现象称为“无限递归错误”(infinite recursion error)。对于函数factorial,val为1是终止条件。

前序遍历

若二叉树非空,则依次执行如下操作:
⑴ 访问根结点;
⑵ 遍历左子树;
⑶ 遍历右子树。

中序遍历

若二叉树非空,则依次执行如下操作:
⑴遍历左子树;
⑵访问根结点;
⑶遍历右子树。

后序遍历

若二叉树非空,则依次执行如下操作:
⑴遍历左子树;
⑵遍历右子树;
⑶访问根结点。

层序遍历

设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

 

引用:

https://blog.csdn.net/fuxuemingzhu/article/details/105183554

https://www.cnblogs.com/joelwang/p/10640599.html

https://www.cnblogs.com/liuzhen1995/p/11921771.html

https://leetcode-cn.com/circle/discuss/J8XFse/

https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91/1602879

https://www.jianshu.com/p/c5d48937f2d9

https://en.wikipedia.org/wiki/Binary_tree

https://en.wikipedia.org/wiki/Tree_(data_structure)

https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92

《C++ Primer 中文版》第4版

https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91%E9%81%8D%E5%8E%86/9796049?fr=aladdin

相关文章: