5道较难的链表面试题

注:本篇文章重在思路讲解,不会详细介绍每一步的步骤!!!

注:本篇文章重在思路讲解,不会详细介绍每一步的步骤!!!

一、 链表分割

OJ链接:https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking

题目:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

思路讲解:

        本题有多种解题思路,我相信朋友们看到这道题也能够想出一些方法,例如利用多个指针在原链表上更改、或者是添加一个数组等等,但是我这里只完成其中一种,那就是把一个链表拆分成为两部分,如题目所示,一个链表大于等于x的值,另一个链表小于x的值,通过一个指针在原链表上面遍历比较,不断地尾插其余两个链表。

图解:

        首先:创建两个新表头,以及3个遍历指针,分别用于原链表,和新创建的两个链表。将原链表的数据与x比较,向后存。

         然后:没有操作,只需要等待分配完成

         最后:需要注意一点,无论如何,我们两个链表连接之前或者之后,都需要将大值链表的最后一个节点的指针域置空,否则会成为一个环,无法退出。

 

         不置空的后果:因为8的后面是3,而3要与5连接,最后造成成环错误。

 代码:

ListNode* partition(ListNode* pHead, int x)
{
	// write code here
	//表为空
	if (pHead == NULL)
	{
		return NULL;
	}

	//创建两个链表头
	ListNode* LowHead = (ListNode*)malloc(sizeof(ListNode));
	assert(LowHead);
	ListNode* GreatHead = (ListNode*)malloc(sizeof(ListNode));
	assert(GreatHead);
	ListNode* Pos = pHead;

	//游走指针
	ListNode* LH = LowHead;
	ListNode* GH = GreatHead;

	//划分数据
	while (Pos)
	{
		//小值尾插
		if (Pos->val < x)
		{
			LH->next = Pos;
			LH = LH->next;
		}
		//大值尾插
		else
		{
			GH->next = Pos;
			GH = GH->next;
		}
		Pos = Pos->next;
	}
	//最后一个结点指针域指置空
	GH->next = NULL;

	//连接
	LH->next = GreatHead->next;

	//置空最后一个结点指针域
	pHead = LowHead->next;

	free(LowHead);
	free(GreatHead);

	return pHead;
}

二、链表回文

OJ链接:https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking

题目:对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

样例:1->2->2->1

思路讲解:

        因为本次题目的要求为单链表,单链表却又只有一个指针域,也就代表了我们不能通过前后指针,一个往前走一个往后走的方式解决问题。虽然我们不能往回走,但是我们却可以改变链表本身,也就是找到链表的中间位置,将它后面的结点逆置得1->2->1->2,这样我们就可以通过两个指针对其进行比较判断了。

        该思路需要掌握两个重要知识点,快慢指针的使用以及头插

        快慢指针表示为快指针每次循环走两步,而慢指针每次只走一步,当快指针走到结束时,慢指针必定到达链表中央结点。

        头插作用为,找到中间结点,然后插入在空指针之前,原链表向后遍历,每找到一个值,就头插在新链表里面,最后返回新链表,就会得到一个逆置的链表,此时原链表的长度也被改变,因为它的最后一个结点的指针域被置为了空。

图解:

 代码:

//反转后续链表节点
ListNode* Reverse_Node(ListNode* pHead)
{
	assert(pHead);
	ListNode* ps = pHead;
	ListNode* NewHead = NULL;
	ListNode* temp = NULL;

	//头插
	while (ps)
	{
		temp = ps->next;
		ps->next = NewHead;
		NewHead = ps;
		ps = temp;
	}

	//返回新头
	return NewHead;
}

//判断
bool chkPalindrome(ListNode* pHead) {
	// write code here
	ListNode* fast = pHead;
	ListNode* slow = pHead;

	//快慢指针找中间结点
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	//反转后续结点
	slow = Reverse_Node(slow);

	//回到头结点
	fast = pHead;
	while (slow)
	{
		//值不同不是回文
		if (slow->val != fast->val)
		{
			return false;
		}
		slow = slow->next;
		fast = fast->next;
	}
	//比较相同
	return true;
}

三、找两个链表的公共结点

OJ链接https://leetcode.cn/problems/intersection-of-two-linked-lists/description/

题目:

        给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。图示两个链表在节点 c1 开始相交:

 思路讲解:

        本题如果只是判断是否有相交结点那么他将会很简单,只需要判断两个链表的最后一个节点是否相等即可,但是他需要返回相交起始节点问题就变得困难起来了,因为本身单链表还是不支持向前移动。如果用暴力求解法,每个节点都比较,那么时间复杂度又高了太多,那么我们还是利用一个思想,两个链表若是有相交,那么从相交起的后续长度一定相同。

        所以这一次同样可以利用一个思想,快慢指针,只要长的那一个链表先走几步,直到和短的那一条链表长度相等,再同时走,同时比较,即可得到相交起始结点。

图解:

 代码:

        首先判断两个链表长度,然后得到差值,长的链表先走差值步,最后同步走,其中遇到节点相同条件,返回该节点。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
	//链表为空
	if (!headA || !headB)
	{
		return NULL;
	}

	struct ListNode* Pos_A = headA;
	struct ListNode* Pos_B = headB;
	int n, m;
	n = m = 0;
	//求A和B长度
	while (Pos_A)
	{
		n++;
		Pos_A = Pos_A->next;
	}
	while (Pos_B)
	{
		m++;
		Pos_B = Pos_B->next;
	}
	//求差值
	int gap = abs(n - m);

	//比较两链表长度
	struct ListNode* LongList = headA;
	struct ListNode* ShortList = headB;

	if (m > n)
	{
		LongList = headB;
		ShortList = headA;
	}
    //长链表先走步数
	while (gap--)
	{
		LongList = LongList->next;
	}
    
    //判断是否有交点
	while (LongList)
	{
		if (LongList == ShortList)
		{
			return LongList;
		}
		LongList = LongList->next;
		ShortList = ShortList->next;
	}

	return NULL;
}

四、判断链表是否有环

OJ链接:https://leetcode.cn/problems/linked-list-cycle/description/

题目:给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

 思路讲解:

        本题目本身并不是很难,但如果想不到链表的结束条件,那么它将会变得异常痛苦,别问我为什么知道。

        本题同样是利用快慢指针来编写,并且需要注意快指针和慢指针之间的步长差距一定要为1。之所以用快慢指针是因为,当一个链表有环的时候,快指针会比慢指针提前进入环区域,并且由于环本身没有一个退出条件,那么快指针就只能在环中不断地循环,直到慢指针也进入环。

        当满指针进入环之后,由于二者之间的速度差为1,那么无论如何快指针都会有与慢指针相遇的那一刻,此时相遇点,就是我们的结束条件。

        如果本身没有环,那快指针就会走到NULL,直接退出。

图解:

代码:

bool hasCycle(struct ListNode *head) 
{
	if (!head)
	{
		return false;
	}

	struct ListNode* fast = head;
	struct ListNode* slow = head;

	//如果链表当中有一个环,那么快指针一定会重新与慢指针相遇
	while (fast && fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;

		//相同
		if (fast == slow)
		{
			return true;
		}
	}
	return false;
}

五、第四题的延伸,找到入环点

OJ链接:https://leetcode.cn/problems/linked-list-cycle-ii/description/

题目:如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

 

思路讲解:

        本题我会讲解两种思路,数学公式法和打断环找交点法。

数学公式法:因为我们设定为慢指针的速度为快指针的二分之一,我们也知道慢指针和快指针走过的路程计算公式。所以最后我们只差相遇点到环起始节点和链表起始结点到环起始节点距离。正好通过计算得知,二者距离相等,那么可以直接循环,退出条件为相同结点。

图解:

 代码:

struct ListNode *detectCycle(struct ListNode *head)
{
	if (!head)
	{
		return NULL;
	}

	struct ListNode* fast = head;
	struct ListNode* slow = head;
	struct ListNode* meet = NULL;
	struct ListNode* Pos = head;

	//如果链表当中有一个环,那么快指针一定会重新与慢指针相遇
	while (fast && fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;

		//判断条件不能是两个指针移动之前
		if (fast == slow)
		{
			//相遇点到环起始节点等于链表起始结点到环起始节点距离
			//L = kC - N
			//L为链表起始结点到环起始节点距离
			//kC为相遇点走的圈数
			//N为起始点到相遇点的距离
			meet = fast;
			while (meet != Pos)
			{
				meet = meet->next;
				Pos = Pos->next;
			}
			return Pos;
		}
	}
	return NULL;
}

打断环找交点法:

        该思路的实现方式与我们的第三题完全相同,我先找到相遇点,保存,再将相遇点之前的哪一个结点的指针域置空,就能成功得到条链表,并且我们知道该链表一定有相交结点,就可以复用第三题的代码啦。直接当CV工程师!!哈哈

代码:需要注意,我们判断的是相遇点的前一个结点,如果没有环的情况下直接用next可能有对空地址操作的风险,所以需要加一个条件判断。

struct ListNode *detectCycle(struct ListNode *head)
{
	if (!head)
	{
		return NULL;
	}

	struct ListNode* fast = head;
	struct ListNode* slow = head;
	struct ListNode* meet = NULL;
	struct ListNode* Pos = head;

	//如果链表当中有一个环,那么快指针一定会重新与慢指针相遇
	while (fast && fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;

		//判断条件不能是两个指针移动之前
		if (fast != NULL)
		{
			if (fast->next == slow->next)
			{
				meet = fast->next;
				fast->next = NULL;
				struct ListNode* Pos_A = meet;
				struct ListNode* Pos_B = Pos;
				int n, m;
				n = m = 0;
				//求A和B长度
				while (Pos_A)
				{
					n++;
					Pos_A = Pos_A->next;
				}
				while (Pos_B)
				{
					m++;
					Pos_B = Pos_B->next;
				}
				//求差值
				int gap = abs(n - m);

				//比较两链表长度
				struct ListNode* LongList = meet;
				struct ListNode* ShortList = Pos;

				if (m > n)
				{
					LongList = Pos;
					ShortList = meet;
				}

				while (gap--)
				{
					LongList = LongList->next;
				}

				while (LongList)
				{
					if (LongList == ShortList)
					{
						return LongList;
					}
					LongList = LongList->next;
					ShortList = ShortList->next;
				}
			}
		}
	}
	return NULL;
}

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值