C C++数据结构(三) —,2024年最新2024-2024字节跳动大数据开发面试真题解析

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

由于这是双向链表,那么对于链表中的某一个节点 p,它的后继的前驱是谁?当然还是它自己。

比如,在上图的双向循环链表中,节点 B 的前驱的后继,还是节点 B

即:p->next->prev == p == p->prev->next。(prev 表示 前驱指针next 表示 后继指针)。

这就好比:坐动车时,北京的下一站是天津,那么北京的下一站的前一站是哪里?哈哈哈,当然还是北京。

1. 初始化链表

双向链表是单链表中扩展出来的结构,所以它的很多操作是和 单链表 相同的,甚至理解起来比单链表更简单。

首先,还是先定义一个结点类型,与单向链表相比,双向链表的结点类型中需要多出一个 前驱指针,用于指向前面一个结点,实现双向。

📝 代码示例

typedef int LTDataType; // 存储的数据类型

typedef struct ListNode
{
	LTDataType data; // 数据域
	struct ListNode\* next; // 后驱指针
	struct ListNode\* prev; // 前驱指针
}LTNode;

在初始化之前,先申请一个 带哨兵位 的头结点,头结点的前驱和后继指针都指向自己,使得链表一开始便满足循环。然后再用一个 初始化函数 对链表进行初始化。
在这里插入图片描述

📝 代码示例

//增加一个节点
LTNode\* BuyLTNode(LTDataType x) {
	LTNode\* newnode = (LTNode\*)malloc(sizeof(LTNode));
	if (newnode == NULL) {
		printf("malloc fail\n");
		exit(-1); // 终止程序
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

//双向链表初始化
LTNode\* ListInit() {
	LTNode\* phead = BuyLTNode(0); // 申请一个头结点,不存储有效数据
	/\*起始时只有头结点,让它的前驱和后继都指向自己\*/
	phead->next = phead;
	phead->prev = phead;
	return phead; // 返回头结点
}

2. 打印链表

打印 双向链表 ,是从头结点的后一个结点处开始,向后遍历并打印,直到遍历到头结点处时便停止。

📝 代码示例

//双向链表打印
void ListPrint(LTNode\* phead) {
	assert(phead);

	LTNode\* cur = phead->next; // 从头结点的后一个结点开始打印
	while (cur != phead) { // 当cur指针指向头结点时,说明链表全部打印完成
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

注意:第一个是 头结点,也被称为 带哨兵位 的结点,它是用来 站岗 的,所以不需要打印。

3. 查找元素

给定一个值,在链表中寻找与该值相同的结点,若找到了,则返回结点地址;

若没有找到,则返回空指针(NULL)。

📝 代码示例

//双向链表查找
LTNode\* ListFind(LTNode\* phead, LTDataType x) {
	assert(phead);

	LTNode\* cur = phead->next; // 从头结点的后一个结点开始查找
	while (cur != phead) { // 从头结点的后一个结点开始查找
		if (cur->data == x) {
			return cur; // 返回目标结点的地址
		}
		cur = cur->next;
	}
	return NULL; // 没有找到目标结点
}

4. 插入结点

双向链表的插入操作分为 3 种情况:

(1)头插
(2)尾插
(3)在指定 pos 位置之前插入

🍑 头插

进行头插时,需要申请一个新的结点,将 新结点 插入到 头结点头结点的后一个结点 之间即可。
在这里插入图片描述

动图演示👇
在这里插入图片描述

📝 代码示例

//双向链表头插
void ListPushFront(LTNode\* phead, LTDataType x)
{
	assert(phead);

	LTNode\* newnode = BuyLTNode(x); // 申请一个结点,数据域赋值为 x
	LTNode\* after = phead->next; // 记录头结点的后一个结点位置
	
	/\*建立新结点与头结点之间的双向关系\*/
	phead->next = newnode;
	newnode->prev = phead;
	
	//建立新结点与beHind结点之间的双向关系
	newnode->next = after;
	after->prev = newnode;
}

注意:第一个是 哨兵结点,所以不能在它的前面插入。

🍑 尾插

尾插也是一样,申请一个新结点,将新结点插入到头结点和头结点的前一个结点之间即可。

因为链表是循环的,头结点的前驱指针直接指向最后一个结点,所以我们不必遍历链表找尾。
在这里插入图片描述

动图演示👇
在这里插入图片描述

📝 代码示例

//双向链表尾插
void ListPushBack(LTNode\* phead, LTDataType x) {
	assert(phead);

	LTNode\* tail = phead->prev; //尾节点就是头节点的前驱指针
	LTNode\* newnode = BuyLTNode(x); // 申请一个结点,数据域赋值为x
	
	/\*建立新结点与头结点之间的双向关系\*/
	newnode->next = phead;
	phead->prev = newnode;
	
	//建立新结点与tail结点之间的双向关系
	tail->next = newnode;
	newnode->prev = tail;
}

🍑 指定位置插入

这里的指定插入,是在指定的 pos 位置的 前面 插入结点。

这里 pos 是指定位置的 地址,那么如何得到这个地址呢?很简单,需要用到上面的 查找函数

然后,我们只需申请一个新结点插入到 指定位置结点 和其 前一个结点 之间即可。

动图演示👇
在这里插入图片描述

📝 代码示例

//双向链表在pos的前面进行插入
void ListInsert(LTNode\* pos, LTDataType x) {
	assert(pos);

	LTNode\* newnode = BuyLTNode(x); // 申请一个结点,数据域赋值为x
	LTNode\* posPrev = pos->prev; // 记录 pos 指向结点的前一个结点
	
	/\*建立新结点与posPrev结点之间的双向关系\*/
	posPrev->next = newnode;
	newnode->prev = posPrev;
	
	
	/\*建立新结点与pos指向结点之间的双向关系\*/
	newnode->next = pos;
	pos->prev = newnode;
}

🍑 插入升级

我们仔细回顾一下,刚刚写的 头插尾插,有没有发现什么规律呢?

没错,头插 其实就是在 哨兵结点下一个结点 插入数据,所以我们可以用上面写的 ListInsert 函数来实现。

📝 代码升级

//头插升级
void ListPushFront(LTNode\* phead, LTDataType x) {
	assert(phead);
	ListInsert(phead->next, x);
}

那么 尾插 呢?其实就是在 哨兵结点前面 插入结点。

📝 代码升级

//尾删升级
void ListPopBack(LTNode\* phead) {
	assert(phead);
	ListErase(phead->prev);
}

5. 删除结点

双向链表的删除操作分为 3 种情况:

(1)头删
(2)尾删
(3)在指定 pos 位置删除

🍑 头删

头删,即释 哨兵结点 的后一个结点,并建立 哨兵结点被删除结点的后一个结点 之间的双向关系即可。
在这里插入图片描述

动图演示👇
在这里插入图片描述

📝 代码示例

//双链表头删
void ListPopFront(LTNode\* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode\* after = phead->next; // 记录头结点的后一个结点
	LTNode\* newAfter = after->next; // 记录after结点的后一个结点
	
	/\*建立头结点与newAfter结点之间的双向关系\*/
	phead->next = newAfter;
	newAfter->prev = phead;
	
	free(after); // 释放after结点
}

🍑 尾删

尾删,即释放最后一个结点,并建立 哨兵结点被删除结点的前一个结点 之间的双向关系即可。
在这里插入图片描述

📝 代码示例

//双链表尾删
void ListPopBack(LTNode\* phead) {
	assert(phead);
	assert(phead->next != phead); //检查链表是否为空

	LTNode\* tail = phead->prev; //记录头结点的前一个结点
	LTNode\* newTail = tail->prev; // 记录tail结点的前一个结点

	free(tail); // 释放tail结点
	tail = NULL;
	
	/\*建立头结点与newTail结点之间的双向关系\*/
	newTail->next = phead;
	phead->prev = newTail;
}

🍑 指定位置删除

删除指定 pos 位置的结点,这里不是删除 pos 前面的结点,也不是删除 pos 后面的结点,而是删除 pos 地址的结点。

同样,释放掉 pos 位置的结点后,建立该结点前一个结点和后一个结点之间的双向关系即可。

动图演示👇
在这里插入图片描述

📝 代码示例

//双向链表删除pos位置的节点
void ListErase(LTNode\* pos) {
	assert(pos); //pos不为空

	LTNode\* posPrev = pos->prev; // 记录pos指向结点的前一个结点
	LTNode\* posNext = pos->next; // 记录pos指向结点的后一个结点

	free(pos); //free是把指针指向的节点还给操作系统
	pos = NULL;
	
	/\*建立posPrev结点与posNext结点之间的双向关系\*/
	posPrev->next = posNext;
	posNext->prev = posPrev;
}

🍑 删除升级

同样,对于 头删尾删 还是可以进行简化。

头删 实际上就是删除 哨兵结点下一个结点

📝 代码升级

//头删升级
void ListPopFront(LTNode\* phead) {
	assert(phead);
	assert(phead->next != NULL); //只有一个节点的时候,就别删了

	ListErase(phead->next);
}

尾删 实际上就是删除 哨兵结点前一个结点

📝 代码升级

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

代码升级

//头删升级
void ListPopFront(LTNode\* phead) {
	assert(phead);
	assert(phead->next != NULL); //只有一个节点的时候,就别删了

	ListErase(phead->next);
}

尾删 实际上就是删除 哨兵结点前一个结点

📝 代码升级

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-IDul4bOv-1713696793736)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C/C++ 是应用广泛的编程语言,其在数据结构应用方面也十分重要。面试中相关的 C/C++ 数据结构问题主要围绕数组、链表、二叉树和图等方面。以下是一些常见问题及其解答: 1. 如何反转一个单向链表? 答:可以使用个指针来实现:cur 代表当前节点,pre 代表上一个节点,next 代表下一个节点。每次遍历时,将 cur 的 next 指向 pre,然后将个指针分别向后移动即可。 2. 如何判断两个链表是否相交,并找出相交的点? 答:可以分别遍历两个链表,得到各自的长度。然后让长的链表先走 n 步,使得两个链表剩余的长度相等。接下来同时遍历两个链表,比较节点是否相同即可找出相交的点。 3. 如何判断一个二叉树是否为平衡二叉树? 答:可以计算每个节点的左右子树深度差,如果任何一个节点的深度差大于1,则此树不是平衡二叉树。可以使用递归实现,每次计算当前节点的深度,然后递归判断其左右子树是否平衡。 4. 如何实现图的深度优先搜索(DFS)和广度优先搜索(BFS)算法? 答:DFS 可以使用递归实现。从某个节点开始,逐个访问其未被访问的邻接节点,并将其标记为已访问。然后对每个未被访问的邻接节点递归调用 DFS 函数。BFS 可以使用队列实现。从某个节点开始,将其加入队列,并标记为已访问。然后从队列中弹出节点,并访问其所有未被访问的邻接节点,并将其加入队列中。重复此过程直到队列为空。 以上是一些常见的 C/C++ 数据结构面试问题及其解答。在面试中,除了掌握相关算法和数据结构知识外,还需多做练习和积累经验,才能更好地应对各种面试问题。 ### 回答2: C语言是一种用于编写系统级程序的高级编程语言,具有简单、高效、灵活等特点,是许多操作系统、编译器等软件的首选语言,也是许多企业在进行面试时重点考察的技能。在C/C++数据结构面试题中,经常会涉及到各种数据结构相关的算法和应用,测试面试者的算法思维能力和实现能力。 其中,常见的数据结构包括链表、栈和队列、二叉树、搜索树、哈希表等。在面试时,会常常涉及代码设计和实现,比如实现链表的插入、删除、查找操作,实现二叉树的遍历、查找操作等。 此外,在数据结构面试中,还经常涉及排序和查找算法,如冒泡排序、快速排序、归并排序、二分查找、哈希查找等。同时,面试者还需要解决一些较为复杂的算法问题,如图的最短路径问题,最小生成树问题等。 总之,C/C++数据结构面试题涵盖了运用数据结构的各种算法和实现方法,需要面试者具备扎实的编程基础和算法思维能力。在备战面试时,可以多做练习,熟悉常用的数据结构和算法,提高理解和实现能力,从而更好地应对面试挑战。 ### 回答3: 面试过程中常见的C/C++数据结构面试题有很多。以下就介绍几个常见的题目并给出解答。 1. 求两个有序数组的中位数 题目描述:给定两个升序排列的整形数组,长度分别为m和n。实现一个函数,找出它们合并后的中位数。时间复杂度为log(m+n)。 解答:这个问题可以使用二分法求解。首先,我们可以在两个数组中分别选出所谓的中间位置,即(i+j)/2和(k+l+1)/2,其中i和j分别是数组A的起始和结束位置,k和l分别是数组B的起始和结束位置。判断A[i+(j-i)/2]和B[k+(l-k)/2]的大小,如果A的中间元素小于B的中间元素,则中位数必定出现在A的右半部分以及B的左半部分;反之,则必定出现在A的左半部分以及B的右半部分。以此类推,每一次都可以删去A或B的一半,从而达到对数级别的时间复杂度。 2. 堆排序 题目描述:对一个长度为n的数组进行排序,时间复杂度为O(nlogn)。 解答:堆排序是一种常用的排序算法,在面试中也经常被考察。堆排序的具体过程是首先将数组构建成一个最大堆或最小堆,然后不断将堆顶元素与最后一个元素交换,并将最后一个元素从堆中剔除。这样,每次剔除后,堆都会重新调整,使得剩下的元素仍然保持堆的性质,直到堆中只剩下一个元素为止。 3. 链表反转 题目描述:反转一个单向链表,例如给定一个链表: 1->2->3->4->5, 反转后的链表为: 5->4->3->2->1。 解答:链表反转题目也是非常常见,其思路也比较简单。遍历链表,将当前节点的next指针指向前一个节点,同时记录当前节点和前一个节点,直至遍历到链表末尾。 以上这个问题分别从二分法、堆排序和链表个方面介绍了常见的C/C++数据结构面试题,希望能帮助面试者更好地准备面试

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值