2022.4.11 链表相关面试题

69 篇文章 3 订阅
21 篇文章 0 订阅

1. 逆置

① 借助头结点
原始单链表
在这里插入图片描述
逆置后
在这里插入图片描述

void Reverse1(struct Node* plist)
{
	assert(plist != NULL);
	if (plist == NULL)
	{
		return;
	}
	PNode p = plist->next;
	PNode q;
	plist->next = NULL;
	while (p != NULL)
	{
		q = p->next;
		p->next = plist->next;
		plist->next = p;
		p = q;
	}

}

② 不借助头结点
第一步:先将头结点 剔除循环
第二步:通过pqr指针依次将所有节点方向颠倒过来
第三步:最后再将头结点连接过来即可

//逆置2(需要三个临时指针)
void Reverse2(struct Node* plist)
{
	assert(plist != NULL);//确保头结点
	assert(plist->next != NULL);//确保首节点存在
	assert(plist->next->next != NULL);//确保第二个节点存在
	PNode p = plist->next;
	PNode q = p->next;
	PNode r;

	p->next = NULL;
	while (q != NULL)//pqr中用q!=NULL来做判断依据
	{
		r = q->next;
		q->next = p;
		p = q;
		q = r;
	}
	plist->next = p;

}

运行结果:
在这里插入图片描述

2. 判断两个单链表是否存在交点,如果存在交点,则找到相交的第一个点

相交图:
在这里插入图片描述
两个单链表发生交点,只可能是上述这种情况,不可能出现数学中的交叉相交。即a点为两个单链表p和q的相交点。
① 如果面试官没有让找相交的点,只是问是否相交(可以用一个非常简单的方法)

//确定两个单链表是否相交(用两个指针跑到两个单链表的尾结点,判断是否在同一个尾结点)
bool Intersect(PNode plist1, PNode plist2)
{

	//assert()
	PNode p1 = plist1->next;
	PNode p2 = plist2->next;
	for (p1; p1 != NULL; p1 = p1->next);//此时,for循环结束,p1在plist1这个单链表的尾结点
	for (p2; p2 != NULL; p2 = p2->next);//此时,for循环结束,p2在plist2这个单链表的尾结点
	return p1 == p2;
}
int main()
{
	Node head1;
	Init_list(&head1);
	for (int i = 0; i < 10; i++)
	{
		Insert_pos(&head1, i, i + 1);
	}
	Show(&head1);

	Node head2;
	Init_list(&head2);
	for(int i = 0; i < 20; i++)
	{
		Insert_pos(&head2, i, i + 101);
	}
	Show(&head2);
	bool tag1 = Intersect(&head1, &head2);
	if (tag1)
	{
		printf("相交\n");
	}
	else
	{
		printf("不相交\n");
	}
    //实现手动相交
	PNode p = &head2;
	for (int i = 0; i < 12; i++)
	{
		p = p->next;
	}
	
	PNode q = &head1;
	for (int i = 0; i < 5; i++)
	{
		q = q->next;
	}
	p->next = q->next;
	Show(&head1);
	Show(&head2);//手动相交,发生数据丢失
	bool tag3 = Intersect(&head1, &head2);
	if (tag3)
	{
		printf("相交\n");
	}
	else
	{
		printf("不相交\n");
	}
	return 0;
}

在这里插入图片描述

最后一行是手动相交,可以看出来,手动相交之后,数据会发生丢失,不建议这种写法

② 如果面试官既要判断是否相交,还要问相交点在那(另外一种解题思路)
思路:首先,申请两个临时指针p和q,让p指向教长的单链表开头,让q指向较短的单链表开头(相等的话无所谓);然后,让p先走几步(两个单链表的长度的差值),这个时候,p和q分别到相交节点的距离就相同了;再用循环去判断(p和q是否为同一个节点,如果不是,则p和q分别向后走,直到退出)

//确定两个单链表是否相交,并且相交的话,返回第一个相交点
struct Node* Intersect_get_Node(PNode plist1, PNode plist2)
{
	//assert;
	int len1 = Getlength(plist1);
	int len2 = Getlength(plist2);
	//接下来,保证p执行较长的那个单链表,用三目元算符
	PNode p = len1 >= len2 ? plist1 : plist2;
	PNode q = len1 >= len2 ? plist2 : plist1;
	
	for (int i = 0; i < abs(len1 - len2); i++)
	{
		p = p->next;
	}
	//此时,p已经将差值跑完,这个时候只需要循环判断p和q是否是同一个节点即可
	while (p != q)//这里不会是死循环,要么p和q指向有效地址,要么p=q==null,退出循环
	{
		p = p->next;
		q = q->next;
	}
	return p;
}
int main()
{
	Node head1;
	Init_list(&head1);
	for (int i = 0; i < 10; i++)
	{
		Insert_pos(&head1, i, i + 1);
	}
	Show(&head1);

	Node head2;
	Init_list(&head2);
	for(int i = 0; i < 20; i++)
	{
		Insert_pos(&head2, i, i + 101);
	}
	Show(&head2);
	PNode p = &head2;
	for (int i = 0; i < 12; i++)
	{
		p = p->next;
	}
	
	PNode q = &head1;
	for (int i = 0; i < 5; i++)
	{
		q = q->next;
	}
	p->next = q->next;
	Show(&head1);
	Show(&head2);//手动相交,发生数据丢失
	bool tag1 = Intersect(&head1, &head2);
	if (tag1)
	{
		printf("相交\n");
	}
	else
	{
		printf("不相交\n");
	}
	struct Node* tmp1 = Intersect_get_Node(&head1, &head2);
	if (tmp1 != NULL)
	{
		printf("相交点有效值为: %d\n", tmp1->data);
	}

    return 0;
}

运行结果:
在这里插入图片描述

3. 任意删除一个节点(要求时间复杂度为O(1),给的这个节点的地址不能是尾结点)

//删除任意一个节点(这个节点不能是尾结点)
bool Del_Node(struct Node* p)
{
	assert(p != NULL);//确保p存在
	assert(p->next != NULL);//确保p不是尾节点

	PNode q = p->next;
	p->data = q->data;
	p->next = q->next;
	free(q);

	return true;
}

在这里插入图片描述
从运行结果可以看出,第一个有效数据节点删除了

4. 判断一个单链表是否存在环?如果确实存在环,则找到入环点

思路:① 需要两个指针,快指针和慢指针,快指针一次走两步,慢指针一次走一步
②让快指针向后走
③循环要么指针走到NULL退出,要么快慢指针在环中相遇
设头结点到入环点的距离为x;入环点到相遇点的距离为y;相遇点,接着向后走,再次遇到入环点的距离为z
其中存在数学关系:
fast: x+n(y+z) //n代表相遇前饶了多少圈
slow: x+y
slow*2=fast

即: 2(x+y)=x+n(y+z),化简后有 x=(n-1)(y+z)+z

所以,一个指针从头结点向后走,另一个指针从相遇点向后走,

struct Node* Is_circle(struct Node* plist)
{
	//assert;
	PNode fast = plist;
	PNode slow = plist;
	while (fast != NULL && fast != slow)//如果快指针没有走到空,且快慢指针还没有相遇,循环继续在里面运行
	{
		slow = slow->next;
		//fast = fast->next->next;//这种一次走两步,可能会异常,害怕第一次指的next为空
		fast = fast->next;
		if (fast != NULL)
		{
			fast = fast->next;
		}
	}
	//此时,循环退出,要么快指针走到了null,证明没有环,要么快慢指针相遇,证明有环
	if (fast == NULL)//没有环
	{
		return NULL;
	}
	PNode A = plist;//头结点
	PNode B = fast;//相遇点,这是fast=slow
	while (A != B)
	{
		A = A->next;
		B = B->next;
	}
	return A;//返回A和B都行,因为A和B相等,入环点
}
  1. 确定一个单链表是否回文
    回文链表特点:类似这样:(12321 123321)
    在这里插入图片描述
    ① 不是回文链表,因为正着读是12,反着读是21
    ②是回文链表,正着读为12,反着读也为12,并且是偶数回文链表
    ③是回文链表,正着读是123,反着读为123,但是是奇数回文链表
bool Ispail(PNode plist)
{
	//1.最好两个指针,一个从前向后跑,另外一个从后向前跑(双向链表两个都行,单链表后面这个不行)

	//因为单链表不存在一个指针从后向前,所以,处理方法为:将单链表中的数据存放到数组中,然后以同样的方式,去判断这个数组即可
	int len = Getlength(plist);
	ELEM_TYPE* arr = (ELEM_TYPE*)malloc(len * sizeof(ELEM_TYPE));
	assert(arr != NULL);
	//此时,等长数组申请好了,接下来将单链表中的数据放到数组中
	int i = 0;
	PNode p = plist;
	for (p; p->next != NULL; p = p->next)
	{
		arr[i++] = p->data;
	}
	int left = 0;
	int right = len - 1;
	while (left < right)
	{
		if (arr[left] != arr[right])
		{
			free(arr);
			return false;
		}
		left++;
		right--;
	}

	free(arr);//malloc申请的,记得释放
	return true;//中间没有发生左右不相等的情况,说明存在回文

}
  1. 找到单链表倒数第K个节点
    思路:申请两个指针,比如找倒数第二个节点,就让其中一个指针提前走两步,接下来两个指针同时向后走,当刚才先走的那个指针走到NULL,则另一个指针就在我们要找的那个位置上
struct Node* Get_k_node(PNode plist, int K)
{
	//申请两个指针,比如找倒数第二个节点,就让其中一个指针提前走两步,接下来两个指针同时向后走,当刚才先走的那个指针走到NULL,则另一个指针就在我们要找的那个位置上
	assert(plist != NULL );
	assert(K >= 1 && K <= Getlength(plist));
	PNode p = plist;
	PNode q = plist;

	for (int i = 0; i < K; i++)
	{
		p = p->next;
	}
	while (p != NULL)
	{
		q = q->next;
		p = p->next;
	}

	return q;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值