数据结构笔面试总结

涉及的几个部分数据结构部分
数组、栈、链表、队列、树、图

数组

数组是最简单、也是使用最广泛的数据结构。栈、队列等其他数据结构均由数组演变而来。

面试问题:
1、寻找数组中第二小的元素
2、找到数组中第一个不重复出现的整数
3、合并两个有序数组
4、重新排列数组中的正值和负值

可以把栈想象成一列垂直堆放的书。为了拿到中间的书,你需要移除放置在这上面的所有书。这就是LIFO(后进先出)的工作原理。
下列数据结构具有记忆功能的是(C)A.队列B.循环队列C.栈D.顺序表
具有记忆功能的数据结构是栈,原因很简单:后进栈的先出栈,所以你对一个栈进行出栈操作,出来的元素肯定是你最后存入栈中的元素,所以栈有记忆功能。

栈的基本操作

Push——在顶部插入一个元素
Pop——返回并移除栈顶元素
isEmpty——如果栈为空,则返回true
Top——返回顶部元素,但并不移除它

面试中关于栈的常见问题

1、使用栈计算后缀表达式
后缀表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
对于一个算术表达式我们的一般写法是这样的(3 + 4) × 5 – 6,这中写法是中序表达式 ,而后序表达式则是将运算符放在操作数的后面,如3 4 + 5 × 6 –,可以看出后序表达式中没有括号, 只表达了计算的顺序, 而这个顺序恰好就是计算器中的一般计算顺序,给出一个算式,如:(3 * 4-(2+5)) * 4 / 2,其等价的后缀表达式为:3 4 * 2 5 + - 4 * 2 /;计算方法为从左到右扫描后缀表达式,遇到数字则入栈,遇到运算符则将栈中的数字出栈,第一个出栈的数字充当第二个运算数,第二个出栈的数字充当第一个运算数,与运算符作运算,并将结果入栈,最后栈中剩下的那个数就是运算结果。

2、对栈的元素进行排序
编写程序,按升序对栈进行排序(即最大元素位于栈顶)。最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构(如数组)。

假设数据保存在原栈s1中,另设辅助栈s2。数据进行一系列处理后以升序排在栈s1中,那么s1中的数据一定是由s2栈倾倒出的。所以在完成所有数据在s1中升序排列之前数据应该在s2中降序排列,即大元素位于栈底。所以问题转化为从s1中弹出元素输入到s2中并使这些元素降序排列。考虑这一操作的中间过程,假设已经弹出s1的部分元素压入s2中,数据在s2中已排序,s2从栈底到栈顶有5,3,2,1这4个元素,假设当前s1的栈顶元素是4,现在要将4入栈s2,为了保持s2栈中元素的顺序,4应该放入3的下面。这时候s2中的1,2,3都需要弹出给4让出位置,1,2,3只能临时存放在s1中,为了避免s1原先的栈顶元素4被覆盖,可以设临时变量保存栈顶4,然后弹出4.再将s2中小于的4的元素全部弹出到s1中,原先s1的栈顶4先入栈s2,再将1,2,3从s1中还原到s2,这时候s2中的元素还是排好序的,为5,4,3,2,1。

3、判断表达式是否括号平衡
思路:建立一个顺序栈,当表达式中有左括号时将其入栈,当出现右括号时,将栈顶元素出栈,检查与当前右括号是否匹配。最后如果栈为空则表示该表达式中的括号是匹配的。

队列

与栈相似,队列是另一种顺序存储元素的线性数据结构。栈与队列的最大差别在于栈是LIFO(后进先出),而队列是FIFO,即先进先出。

队列的基本操作

Enqueue() —— 在队列尾部插入元素
Dequeue() ——移除队列头部的元素
isEmpty()——如果队列为空,则返回true
Top() ——返回队列的第一个元素

面试中关于队列的常见问题

1、使用队列表示栈
栈实现队列:思路是有两个栈,一个用来放数据(数据栈),一个用来辅助(辅助栈)。数据添加时,会依次压人栈,取数据时肯定会取栈顶元素,但我们想模拟队列的先进先出,所以就得取栈底元素,那么辅助栈就派上用场了,把数据栈的元素依次弹出到辅助栈,但保留最后一个元素,最后数据栈就剩下了最后一个元素,直接把元素返回,这时数据栈已经没有了数据。最后呢,把辅助栈的元素依次压人数据栈,这样,我们成功取到了栈底元素。

队列实现栈
思路同上:有数据队列和辅助队列,模拟栈的先进后出,队列是队尾进队头出,也就是说每次取值要取队列的队尾元素,数据队列出队到辅助队列,留下最后一个元素返回,辅助队列再把元素出队到数据队列

2、对队列的前k个元素倒序
实现方法为每次遍历队列,从中找出最小的元素,放入临时队列,遍历的过程是出队的过程,注意如果一个元素比当前的最小值大,则要放回队列当中,如果比当前的最小值小,则保存起来,暂时不放回队列中,发现更小的,把原来的最小值放入,更新最小值,在遍历完一次以后,将最小值存入临时队列。然后开始第二次遍历,注意每次遍历原队列中都会减少一个元素,因此共遍历队列N次,每次对队列N、N-1、N-2 ... 1这么多次出队操作来找最小值,在最后一次完成后临时队列中存放的就是排序好的结果,出队N次即可按非降序输出。

3、使用队列生成从1到n的二进制数
十进制数化二进制数:整数部分用“除二取余“法,将数除以2,将余数放入栈,商再除以2,重复。直至商为0。小数部分用“乘二取整”法,数乘2,然后取整、放入队列里

链表

链表是另一个重要的线性数据结构,乍一看可能有点像数组,但在内存分配、内部结构以及数据插入和删除的基本操作方面均有所不同。

链表就像一个节点链,其中每个节点包含着数据和指向后续节点的指针。 链表还包含一个头指针,它指向链表的第一个元素,但当列表为空时,它指向null或无具体内容。
链表一般用于实现文件系统、哈希表和邻接表。

链表按方向分包括以下类型:

单链表(单向)
双向链表(双向)

链表的基本操作:

InsertAtEnd - 在链表的末尾插入指定元素
InsertAtHead - 在链接列表的开头/头部插入指定元素
Delete  - 从链接列表中删除指定元素
DeleteAtHead - 删除链接列表的第一个元素
Search  - 从链表中返回指定元素
isEmpty - 如果链表为空,则返回true

面试中关于链表的常见问题

1、反转链表
链表反转单向链表的反转是一个经常被问到的一个面试题,也是一个非常基础的问题。比如一个链表是这样的: 1->2->3->4->5 通过反转后成为5->4->3->2->1。最容易想到的方法遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往后面继续遍历
还有一种利用递归的方法。这种方法的基本思想是在反转当前节点之前先调用递归函数反转后续节点。

2、检测链表中的循环
判断链表是否存在环型链表问题:判断一个链表是否存在环,例如下面这个链表就存在一个环:
例如N1->N2->N3->N4->N5->N2就是一个有环的链表,环的开始结点是N5这里有一个比较简单的解法。设置两个指针p1,p2。每次循环p1向前走一步,p2向前走两步。直到p2碰到NULL指针或者两个指针相等结束循环。如果两个指针相等则说明存在环。

3、返回链表倒数第N个节点
思路很简单,只有两种出现的情况:
1、链表的长度刚刚好等于n,也就是说删除表头节点,
2、链表长度大于n,那么我们先定义两个表头,一个后移n位,然后两个链表同时后移,这时当后面的节点到达尾部时,前面的节点就是删除的节点的前一个节点。

4、删除链表中的重复项
思路一:
遍历链表,把遍历的值存储到一个Hashtable中,在遍历过程中,若当前访问的值在Hashtable中已经存在,则说明这个数据是重复的,因此就可以删除。
优点:时间复杂度较低O(n)
缺点:在遍历过程中需要额外的存储空间来保存已遍历过的值
思路二:
对链表进行双重循环遍历,外循环正常遍历链表,假设外循环当前遍历的结点为cur,内循环从cur开始遍历,若碰到与cur所指向结点值相同,则删除这个重复结点
优点:不需要额外的存储空间
缺点:时间复杂度较高O(n^2)
思路三:
外循环当前遍历的结点为cur,内循环从链表头开始遍历至cur,只要碰到与cur值相同的结点就删除该结点,同时内循环结束,因为与cur相同的结点只可能存在一个(如果存在多个,在前面的遍历过程中已经被删除了)
优点:采用这种方法在特定的数据发布的情况下会提高算法的时间复杂度

树形结构是一种层级式的数据结构,由顶点(节点)和连接它们的边组成。 树类似于图,但区分树和图的重要特征是树中不存在环路。
树形结构被广泛应用于人工智能和复杂算法,它可以提供解决问题的有效存储机制。

以下是树形结构的主要类型:

N元树、平衡树、二叉树、二叉搜索树、AVL树、红黑树、2-3树
二叉树和二叉搜索树是最常用的树

二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:
1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2)若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3)任意节点的左、右子树也分别为二叉查找树。
二叉查找树性质:对二叉查找树进行中序遍历,即可得到有序的数列。

面试中关于树结构的常见问题:

1、求二叉树的高度
算法一:采用后序遍历二叉树,结点最大栈长即为二叉树的高度;
算法二:层次遍历二叉树,最大层次即为二叉树的高度;
算法三:采用递归算法,求二叉树的高度

2、在二叉搜索树中查找第k个最大值
解题思路:通过中序遍历二叉搜索树,找到第k大的节点的值temp,然后通过层次遍历二叉搜索树,找到值为temp的节点,并且返回该节点
注意考虑:
1)二叉搜索树为空,或者k为0则直接返回null;
2)k的值大于二叉搜索树的节点个数,则返回null
3)查找与根节点距离k的节点
4)在二叉树中查找给定节点的祖先节点
这道题的传统思路是想方法把根到两个点的路径分别保存在两个容器中,然后从后往前遍历容器找出相等的节点便为最近公共祖先。很容易计算出这是一个时间复杂度为o(n),空间复杂度为o(lgn)的算法。
如果我们这时要求使用空间复杂度为o(1)的算法
我们可以这样做: 假设2个节点为p1, p2。
我们遍历这颗树,如果发现这个节点为p1/p2或者这个
节点的子树中有p1/p2时,返回p1/p2。否则为NULL.
在这里注意,如果一个节点的左右子树都返回一个非NULL值,那么这个节点一定为p1,p2的最近公共祖先。 这时我们返回这个节点。如果p1为p2的祖先,那么遍历到p1时便会返回而不会遍历p1子树。那么不会有节点的左右子树都不为NULL,只会返回p1,而p1正好是它们的最近公共祖先。

图是一组以网络形式相互连接的节点。节点也称为顶点。 一对节点(x,y)称为边(edge),表示顶点x连接到顶点y。边可以包含权重/成本,显示从顶点x到y所需的成本。

图的类型

无向图
有向图

在程序语言中,图可以用两种形式表示:

邻接矩阵
邻接表

常见图遍历算法

广度优先搜索
深度优先搜索

面试中关于图的常见问题

1、实现广度和深度优先搜索
深度优先搜索
(1)用栈记录下一步的走向。访问一个顶点的过程中要做三件事:
①访问顶点
②顶点入栈,以便记住它
③标记顶点,以便不会再访问它
(2)访问规则:
a.如果可能,访问一个邻接的未访问顶点,标记它,并入栈。
b.当不能执行a时(没有邻接的未访问顶点),如果栈不为空,就从栈中弹出一个顶点。
c.如果不能执行规则a和b,就完成了整个搜索过程。
(3)实现:基于以上规则,循环执行,直到栈为空。每次循环各种,它做四件事:
①用peek()方法检查栈顶的顶点。
②试图找到这个顶点还未访问的邻接点。
③如果没有找到,出栈。
④如果找到这样的顶点,访问并入栈。

广度优先搜索(BFS):
(1)用队列记录下一步的走向。深度优先搜素表现的好像是尽快远离起点似的,相反,广度优先搜索中,算法好像要尽可能靠近起始点。
(2)访问规则:
a.访问下一个未访问的邻接点(如果存在),这个顶点必须是当前顶点的邻接点,标记它,并入队列。
b.如果因为已经没有未访问顶点而不能执行规则a,那么从队列头取一个顶点(如果存在),并使其成为当前顶点。
c.如果因为队列为空而不能执行规则b,则完成了整个搜索过程。
(3)实现:基于以上规则,对下图做bfs遍历

深度优先搜素算法:不全部保留结点,占用空间少;有回溯操作(即有入栈、出栈操作),运行速度慢。
广度优先搜索算法:保留全部结点,占用空间大; 无回溯操作(即无入栈、出栈操作),运行速度快。

2、检查图是否为树

一个无向图G是一棵树的条件是G必须是无回路的连通图或是有n-1条边的连通图,这里采用后者实现。
在深度搜索遍历的过程中,同时对遍历过的顶点和边数计数,当全部顶点都遍历过且边数为2∗(n−1)2∗(n−1)时,这个图就是一棵树,否则不是一棵树。

2)计算图的边数
无向图的任何2个不同的节点都可以有一条邻接边.
结点个数为m,图中的边数为从m中取2的组合数,为 m(m-1)/2.

3)找到两个顶点之间的最短路径
迪杰斯特拉算法计算的是有向网中的某个顶点到其余所有顶点的最短路径;弗洛伊德算法计算的是任意两顶点之间的最短路径。

迪杰斯特拉算法解决的是从网中的一个顶点到所有其它顶点之间的最短路径,算法整体的时间复杂度为O(n2)。但是如果需要求任意两顶点之间的最短路径,使用迪杰斯特拉算法虽然最终虽然也能解决问题,但是大材小用,相比之下使用弗洛伊德算法解决此类问题会更合适。

迪杰斯特拉算法的解决思路是:以每一个顶点为源点,执行迪杰斯特拉算法。这样可以求得每一对顶点之间的最短路径。
弗洛伊德的核心思想是:对于网中的任意两个顶点(例如顶点 A 到顶点 B)来说,之间的最短路径不外乎有 2 种情况:
1、直接从顶点 A 到顶点 B 的弧的权值为顶点 A 到顶点 B 的最短路径;
2、从顶点 A 开始,经过若干个顶点,最终达到顶点 B,期间经过的弧的权值和为顶点 A 到顶点 B 的最短路径。

字典树(这是一种高效的树形结构,但值得单独说明)

字典树,也称为“前缀树”,是一种特殊的树状数据结构,对于解决字符串相关问题非常有效。它能够提供快速检索,主要用于搜索字典中的单词,在搜索引擎中自动提供建议,甚至被用于IP的路由。
以下是在字典树中存储三个单词“top”,“so”和“their”的例子:
这些单词以顶部到底部的方式存储,其中绿色节点“p”,“s”和“r”分别表示“top”,“thus”和“theirs”的底部。

面试中关于字典树的常见问题

计算字典树中的总单词数
打印存储在字典树中的所有单词
使用字典树对数组的元素进行排序
使用字典树从字典中形成单词
构建T9字典(字典树+ DFS )

散列表(哈希表)

哈希法(Hashing)是一个用于唯一标识对象并将每个对象存储在一些预先计算的唯一索引(称为“键(key)”)中的过程。因此,对象以键值对的形式存储,这些键值对的集合被称为“字典”。可以使用键搜索每个对象。基于哈希法有很多不同的数据结构,但最常用的数据结构是哈希表。
哈希表通常使用数组实现。

散列数据结构的性能取决于以下三个因素:

哈希函数
哈希表的大小
碰撞处理方法

面试中关于哈希结构的常见问题:

在数组中查找对称键值对
追踪遍历的完整路径
查找数组是否是另一个数组的子集
检查给定的数组是否不相交

容易考察的几个应用问题

数据结构部分:
约瑟夫环 循环链表(双向循环链表)
迷宫游戏(图--迪杰斯特拉算法和弗洛伊德算法)
八皇后问题(回溯法)
汉诺塔问题(递归问题)
出入口为一个的停车场问题(栈、队列)

具体笔面试题(提高版):

  1. 说说红黑树?及其与AVL树对比
    答:路径,红节点的左右子树一定为黑,根节点一定为黑,叶节点(nul)节点必须为黑,节点不是红就是黑。与AVL树的对比,降低了高度的严格性对时间进行了优化,而AVL树的严格高度平衡耗费了更多的时间,这种时间主要在插入和删除过程中,但是如果查询操作特别多而动态调整很少那么AVL是更好的选择,否则红黑树更好。

  2. 哈希冲突?怎样解决?
    答:
    开地址法,链地址法,再哈希法,公共溢出区

3.(平衡多路查找树)b树,b+树,b树?
答:
当数据非常多平衡二叉查找树会比较深,就会出现很多io操作,非常耗时性能达到瓶颈,所以出现了平衡多路查找树,b树数据信息分布在整棵树上,b+树是数据信息分布在叶子节点上,而且叶子节点连成一个链表(例如跳表),适合范围性数据查找,b
树利用了b+树的空指针建立了兄弟节点,用于分裂节点的时候(类似于线索二叉树)

  1. 位图法,布隆过滤器进行排序和去重和前top?
    答:
    都可以进行去重,位图使用1个或者布隆过滤器使用了多个哈希函数,进行排序位图可以实现(申请n/32+1个大小的bytes数组,每32个位置代表int数组的1个位置,0-31,32-63....,数字如果出现位置就置1),前top问题:小顶堆,分治patiction,更精妙的是冒泡排序(每次可确定一个当前的最大值)。

  2. 中缀表达式转为后缀表达式,前缀表达式?
    答:
    转化为后缀表达式从前向后,转化为前缀表达式从后向前(转化为了后缀表达式求解),遇见数字直接加入字符串,遇见左括号直接入栈,右括号弹栈直到左括号出现,遇见符号如果栈尾的符号优先级小那么直接入栈,否则符号出栈直到栈尾元素优先级小于此符号,特殊的左括号优先级最小。

  3. 堆的插入和删除?
    答:
    插入放入堆尾进行从底向上进行调整,删除的那个元素与堆底元素,然后对交换上去的元素向下进行调整进行堆化

  4. 堆和栈的区别?
    答:空间分配,栈是自动分配,堆是需要手动申请,数据类型先进后出的线性结构,树形结构。

  5. 对一千万个整数排序,整数范围[-1000,1000]间,如何排序最快?
    先映射到正整数上,计算每个位置上的个数构建一个计数信息表,在通过计数信息表构建一个位置表(比如:数值1000有10个前1000的数有200个,那么数值1000的位置信息表对应的值就是205个),从后向前便利数字在对应的位置表上个数减一(从前向后处理不方便)。

  6. 第k大的数?
    答:
    快排可以转化为前k大的问题,构建最小堆k个元素最后堆顶元素就是。

  7. 最小生成树的prim和kruskal?
    答:
    prim每次连接顶点与树之间的最小距离(选最短的顶点,需要优先级队列),kruskal每次给顶点加最小权重的边只要不构成环(选最短的边,使用并查集主要利用递归实现路径压缩以及头目合并)。

  8. 亿级文件进行取交集?
    答:布隆过滤器进行判重来达到查找交集的效果但是会有小小的损失因为判定为重复可能不是重复因为出现了冲突但是判定为不是重复就一定不重复要是真要实现完全精准查在进行暴力检查也可以

  9. 如何快速找出两个队列中相同的元素,假设队列的长度非常大?
    答:
    1、直接hash,对一个队列元素经过hash进行存储,用另外一个队列计算hash值然后对比!(空间消耗大)
    2、布隆过滤器,创建位图只有0/1,生成多个hash函数每个函数计算一个值进行映射到k个位置记为1,然后一直这样处理即可如果映射到的值已经全是1了那就是重复的,但是会有误判的,不是重复的那么一定不是重复的是重复的可能实际上不重复(hash碰撞)!(堆空间进行优化)。

13.Dijkstra和最小生成树prim?
答:
prim是每次选取一个节点到树的距离最短生成树,是针对整个图来说构建一个消费最少的结构。
dijkstra是单源点最短路径是每次选取一个距离源点最短的节点以此为节点进行更新每次都会以此节点为基础确定一个距离源点的未确定最短路径节点的最短路径。(适应于有向图权重为正的结构)。

  1. 存储二叉树的结构有哪些?各自的特点是什么?
    答:
    顺序存储可能会浪费空间(在非完全二叉树的时候),但是读取某个指定的节点的时候效率比较高O(0)
    链式存储相对二叉树比较大的时候浪费空间较少,但是读取某个指定节点的时候效率偏低O(nlogn)

  2. 说说关于动态规划的思想?
    答:动态规划简称就是DP,其思想主要在于,本质上是一种划分子问题的算法,站在任何一个子问题的处理上看,当前子问题的提出都要依据现有的类似结论,而当前问题的结论是后 面问题求解的铺垫。任何DP都是基于存储的算法,核心是状态转移方程;
    比较经典的题目就是:“01”背包问题和矩阵相乘问题;

  3. 优先队列,阻塞队列,优先阻塞队列等多种队列的区别?
    答:
    同:队列都不能插入null元素;
    不同:
    优先队列(PriorityQueue):就是在队列中根据某一个特征值自动进行排序,优先队列分为两种,最大优先队列和最小优先队列,优先队列的一个最大特性就是,当插入元素或者删除元素的时候,队列会自动进行调整,保证队首元素一定是优先权最大/最小。正是由于优先队列的这种特性,优先队列可以被用在很多地方,比如作业调度,进程调度等。
    (1)其底层是通过堆来实现(默认是小堆);----具体是小堆还是大堆,需要根据队列元素中的比较器来判断
    (2)就是会对插入的元素进行排序(可以根据队列的元素内容的comparable比较器的设置进行自定义,默认是用的从小到大)
    (3)不允许插入null元素

阻塞队列(BlockingQueue):是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
其实现类有如下几种:

优先阻塞队列(PriorityBlockingQueue):(1)其是阻塞队列的一个实现类(2)其底层与优先队列一致,不过其内部进行了加锁处理(3)不允许插入null元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值