单向链表相交的第一个公共结点, 判断链表是否有环以及环的入口节点

本文介绍了一种算法,用于查找两个单向链表的第一个公共节点。通过计算链表长度并调整遍历起点,确保同时到达交点,还探讨了如何检测链表中的环及其入口节点。
//////题目:两个单向链表,找出它们的第一个公共结点。
//////
//////链表的结点定义为:
//////
//////struct ListNode
//////
//////{
//////
//////      int       m_nKey;
//////
//////      ListNode*   m_pNext;
//////
//////};
//////
//////分析:这是一道微软的面试题。微软非常喜欢与链表相关的题目,因此在微软的面试题中,链表出现的概率相当高。
//////如果两个单向链表有公共的结点,也就是说两个链表从某一结点开始,它们的m_pNext都指向同一个结点。
//////但由于是单向链表的结点,每个结点只有一个m_pNext,因此从第一个公共结点开始,
//////之后它们所有结点都是重合的,不可能再出现分叉。所以,两个有公共结点而部分重合的链表,
//////拓扑形状看起来像一个Y,而不可能像X。

#include <iostream>
#include <algorithm>
using namespace std;

#define NSIZ 101

struct ListNode 
{ 

	int m_nKey; 
	ListNode*  m_pNext; 

}; 

//获得链表长度
int GetListLength(ListNode * head)
{
	if(!head)
	{
		return 0;
	}
	int len = 0;
	ListNode * tmp = head;

	while(tmp)
	{
		++len;
		tmp = tmp->m_pNext;
	}
	return len;
}

//尾插法构造链表
void CreateList(ListNode * & head, int arr[], int n)
{
	ListNode * p = head;
	if(head == 0)
	{
		head = new ListNode();
		head->m_nKey = arr[0];
		p = head;
	}
	int i = 1;
	for(;i < n; ++i)
	{
		ListNode * tmp = new ListNode();
		tmp->m_nKey = arr[i];
		p->m_pNext = tmp;
		p = tmp;
	}
	p->m_pNext = 0;
}

//合并2个链表
ListNode* UnionList(ListNode * head1, ListNode * head2)
{

	if(head1 == 0 && head2)
	{
		return head2;
	}
	else if(head1 && !head2)
	{
		return head1;
	}
	else if(head1 && head2)
	{
		ListNode * pre = head1;
		ListNode * p = pre->m_pNext;
		while(p)
		{
			pre = p;
			p = p->m_pNext;
		}

		pre->m_pNext = head2;
		return head1;
	}
	else
	{
		return 0;
	}
}

//打印链表元素值
void dump(ListNode * head)
{
	ListNode * p = head;
	while(p)
	{
		printf("%d ", p->m_nKey);
		p = p->m_pNext;
	}

	printf("\n");
}

//判断两个链表是否有交点,如果有则返回第一个交点
//如果head1 && head1 == head2那么显然相交,返回head1
//否则 分别获得head1链表和head2链表的长度为len1和len2 ,并且假设len1 > len2
//然后用指针pLong从head1开始向后移sub = len1 - len2步,指针pShort = head2
//然后p1,p2每次前进一步并且比较p1 == p2,若不为空且相等则返回p1,
//否则说明head1链表和head2链表没有交点
ListNode * FirstCommonNode(ListNode * head1, ListNode * head2)
{
	int len1 = GetListLength(head1);
	int len2 = GetListLength(head2);
	ListNode * Short = head1, *Long = head2;
	int sub = len2 - len1;
	if (len1  > len2)
	{
		Short = head2;
		Long = head1;
		sub  = len1 - len2;
	}

	while(sub > 0)
	{
		Long = Long->m_pNext;
		--sub;
	}

	while(Short && Long && Short != Long)
	{
		Short = Short->m_pNext;
		Long = Long->m_pNext;
	}

	if (Short && Long && Short == Long)
	{
		return Short;
	}

	return 0;
}

//检测链表中是否有环,快慢指针方法
//若有环则返回从头结点进入环的第一个节点
//假设p1 = head1, p2 = head->next->next
//首先检查是否有环,有环的话p1,p2必然在环内相遇
//此时从相遇处端口,方法为
//common = p2;
//head2 = p2->next, p2 ->next=  0;
//此时原链表被分成了2个链表,头结点分别为head1, head2
//利用FirstCommonNode(ListNode * head1, ListNode * head2)函数得到相交节点FirtNode
//然后修复链表:common->next= head2
//最后返回FirstNode
ListNode * HasLoop(ListNode * head)
{
	if (!head)
	{
		return 0;
	}

	ListNode * slow = head;
	ListNode * quick = head;
	while(quick && quick->m_pNext)
	{
		slow = slow->m_pNext;
		quick = quick->m_pNext->m_pNext;
		if (slow == quick)
		{
			break;
		}
	}

	// 无环的话
	if (slow != quick )
	{
		return 0;
	}

	// p1表示相遇的点, p2表示p1->next
	//然后把p1->next 置为0
	//那么原链表形成了2个相交链表,一个以head为表头,一个以p2为表头
	ListNode * p1 = quick;
	ListNode * p2 = p1->m_pNext;
	p1->m_pNext = 0;
	ListNode * firtNode = FirstCommonNode(head,p2);

	p1->m_pNext = p2;
	return firtNode;
}


//给定单链表中某一个节点P,删除该节点(P节点不是最后节点)
//方法:首先存放好p中数据,然后把P->next中数据copy到p中,然后把p->next删除。
int DeleteNode(ListNode * p)
{
	if (!p)
	{
		return INT_MIN;
	}

	int tmp = p->m_nKey;
	ListNode * tmpNode = p->m_pNext;
	p->m_nKey = p->m_pNext->m_nKey;
	p->m_pNext = p->m_pNext->m_pNext;

	if (tmpNode != 0)
	{
		delete tmpNode;
		tmpNode = 0;
	}
}

//给定单链表中某一个非空节点P,在节点P前面插入一个节点
//方法:首先分配一个节点q,将q插在p点之后
//然后将p点数据copy到q节点中,然后再把要插入的数据放到p中
int InsertNode(ListNode * p, int data)
{
	if (!p)
	{
		return INT_MIN;
	}

	ListNode * tmpNode = new ListNode();
	tmpNode->m_nKey = p->m_nKey;
	tmpNode->m_pNext = p->m_pNext;
	p->m_pNext = tmpNode;

	p->m_nKey = data;

}

int main()
{

	int arr[] = {1, 2, 3};
	int brr[] = {4, 5, 6};
	int crr[] = {7, 8, 9};
	int alen = sizeof(arr)/sizeof(arr[0]);
	int blen = sizeof(brr)/sizeof(brr[0]);
	int clen = sizeof(crr)/sizeof(crr[0]);

	ListNode * head1 = 0;
	ListNode * head2 = 0;
	ListNode * head3 = 0;

	CreateList(head1, arr, alen);
	CreateList(head2, brr, blen);
	CreateList(head3, crr, clen);

	//合并链表1和链表3
	head1 = UnionList(head1, head3);
	//合并链表2和链表3
	head2 = UnionList(head2, head3);

	//打印链表1
	dump(head1);

	//打印链表2
	dump(head2);

	//寻找两个链表的第一个公共节点
	ListNode *findNode =  FirstCommonNode(head1, head2);
	printf("第一个公共节,地址为:Ox%x 值为:%d\n", findNode, findNode->m_nKey);

	findNode = HasLoop(head1);
	findNode == 0?printf("没有环\n"):printf("有环,换的入口节点值为:%d\n", findNode->m_nKey);
	return 0;
}


<think> 题目要求:实现一个函数,输入单向链表的头结点和整数k,输出链表中倒数第k个结点的指针。 链表结点定义:struct ListNode { int val; ListNode* m_pNext; }; 思路:经典的双指针法(快慢指针) 1. 初始化两个指针都指向头结点。 2. 先让快指针向前移动k步。如果在这个过程中快指针已经变为空(NULL),说明链表长度不足k,返回空指针(异常)。 3. 然后同时移动快指针和慢指针,直到快指针指向最后一个结点(即快指针的next为空)。 4. 此时慢指针指向的结点就是倒数第k个结点。 注意:题目要求有多组输入,但这里我们重点实现函数,输入输出处理可以参考引用中的示例(但注意引用中的方法直接用了数组,而这里要求用链表结构)。 代码实现(C++): ```cpp #include <iostream> using namespace std; struct ListNode { int val; ListNode* m_pNext; ListNode(int x) : val(x), m_pNext(nullptr) {} }; ListNode* findKthFromTail(ListNode* head, int k) { if (head == nullptr || k <= 0) { return nullptr; } ListNode* fast = head; ListNode* slow = head; // 快指针先走k步 for (int i = 0; i < k; i++) { if (fast == nullptr) { // 如果还没走完k步就遇到NULL,说明链表长度小于k,返回空指针 return nullptr; } fast = fast->m_pNext; } // 然后同时移动两个指针,直到快指针指向NULL(此时慢指针指向倒数第k个结点的前一个?) // 但是注意,当快指针指向最后一个结点的下一个(即NULL)时,慢指针正好指向倒数第k个结点。 // 因为快指针先走了k步,然后两个指针一起走了(n-k)步,慢指针总共走了(n-k)步,从0开始,所以位置就是n-k,倒数第k个就是n-k+1?实际上: // 实际上我们让快指针先走k步后,此时快指针在位置k(0-indexed),然后两个指针一起走,当快指针走到末尾(位置n-1)时,再走一步就会到NULL,所以一起走了(n-k)步,慢指针从0走到(n-k),这个位置就是倒数第k个(因为正数位置n-k,后面还剩k-1个结点?不对,后面还剩(n - (n-k) -1)=k-1个?) // 实际上:倒数第k个结点,就是正数第n-k+1个(1-indexed),而0-indexed下标是n-k。 // 但是,我们上面的循结束后,快指针指向的是第k个结点(0-indexed下标为k)?然后我们开始同时移动,直到快指针变为NULL(即走了n-k步),慢指针从0开始走了n-k步,到达位置n-k-1?不对,我们需要修正。 // 修正:实际上,我们让快指针先走k-1步,然后当快指针的下一个不为空时,两个指针一起移动。但另一种做法:我们上面的代码让快指针先走k步,然后同时移动直到快指针为空,此时慢指针指向倒数第k个结点。 // 证明:假设链表长度为n,快指针先走k步,到达第k+1个结点(0-indexed下标为k)。然后两个指针一起移动,每次移动一步,直到快指针移动到NULL(即走了n-k步),慢指针从0开始移动了n-k步,所在位置是n-k(0-indexed),那么正数位置是n-k+1,倒数就是k(因为倒数第1个是位置n,倒着数k个就是n-k+1)。但是,0-indexed下标为n-k的结点,实际上在正数位置是第n-k+1个,倒数就是k个(因为最后一个结点是倒数第1个,往前数k-1个就是它)。所以正确。 // 但是,当k等于链表长度时,快指针先走k步后指向NULL,然后循判断fast不为空的条件就不满足,所以不会进入循,慢指针还在头结点,而头结点就是倒数第n个(即倒数第k个),所以正确。 while (fast != nullptr) { fast = fast->m_pNext; slow = slow->m_pNext; // 注意:这里先移动再判断?不对,这样会多移动一步 // 实际上,我们要在移动之前判断? } // 上面的循会导致慢指针多走了一步?让我们分析: // 初始:fast指向第k个结点(0-indexed下标k),slow指向0。 // 循条件:fast不为空,进入循,然后fast移动到下一个,slow也移动到下一个。 // 那么,当fast指向最后一个结点(下标n-1)时,进入循,然后fast移动到NULL,slow移动到下标1(初始0,然后移动了1次)?不对,我们走了多少次循? // 实际上,快指针从下标k开始,到NULL,一共走了n-k步(因为从下标k到下标n-1有n-k个结点,然后下一步就到NULL,所以循次数是n-k次)。那么慢指针从0开始,每次移动一步,移动了n-k步,到达下标n-k,这个位置就是倒数第k个(因为最后一个结点是下标n-1,倒数第k个下标是n-k,因为下标n-k到n-1有k-1个结点,所以它后面还有k-1个结点,它就是倒数第k个)。 // 但是,上面的循逻辑写错了:循内是先移动指针,然后再判断?不对,循条件是先判断fast不为空才进入,然后移动。那么最后一次:当fast指向最后一个结点(不为空)进入循,然后fast移动变成NULL,slow移动一次。然后下一次循判断fast为NULL,退出。所以循次数就是n-k次,慢指针移动n-k次,从0到n-k(0-indexed),这个位置就是倒数第k个。 // 但是,上面的写法在循体内移动指针,然后下一次循条件判断。所以循次数等于从当前fast开始到NULL之前的结点数。所以实际上是走了n-k步。 // 重新调整:上面的循实际上是不正确的,因为当我们进入循时,fast可能已经为NULL(在k=n时,fast经过第一步循后变为NULL,然后进入循?不对,k=n时,循开始前fast就已经是NULL了(因为先走了k=n步,此时fast为NULL),所以循条件不成立,不会进入循,此时slow还是头结点,即倒数第n个,正确。 // 再验证k=1的情况:先走1步,fast指向第二个结点(如果存在),然后进入循:循条件是fast不为空,所以只要链表长度大于1,就会循直到fast指向NULL。循次数为n-1次,慢指针移动n-1次,从0到n-1,即为最后一个结点,倒数第1个,正确。 // 所以代码应该是: // 上面已经将快指针移动了k步,然后循条件为while(fast != nullptr) { ... } 这样会导致慢指针多移动一次吗? // 实际上,在循体内,先移动指针,然后循条件判断下一次。所以循次数就是当前fast非空的情况下移动的次数。 // 但是,我们希望的是两个指针一起移动,直到快指针指向NULL。注意,此时慢指针还没有移动,所以应该先移动再判断?不对,我们需要在移动前判断。 // 重新考虑:我们让快指针先走k步,此时快指针指向的是正数第k+1个结点(如果存在),然后慢指针指向第一个结点。然后我们让两个指针同时移动,直到快指针指向NULL,这时慢指针正好指向倒数第k个结点。 // 所以循条件应该是:当fast不为空时,两个指针同时向后移动。这样,每次循移动一次,直到fast为空。循次数等于从当前位置(第k步后的位置)到NULL的步数,即n-k步(因为一共n个结点,快指针已经在第k个结点(0-indexed下标k),然后向后走n-k步到NULL)。 // 因此,循体内的移动正好是n-k次,慢指针移动n-k次,从头结点(0)移动到n-k(0-indexed),即为倒数第k个。 // 所以上面的循体正确吗?注意,循体内是先移动指针再判断下一次。但第一次进入循时,fast可能已经为NULL(当k=n时,循前fast已经是NULL,所以不会进入循),所以不会执行循体,此时slow还是头结点,正确。 // 因此,代码可以写成: while (fast != nullptr) { fast = fast->m_pNext; slow = slow->m_pNext; } // 此时slow指向倒数第k个结点。 return slow; } ``` 但是上面的循体在k=n时不会进入,直接返回头结点,正确。 在k>n时,我们在第一步的for循中就会返回空指针。 但是,上面的代码在循体内,每次都是先移动指针,然后循条件判断是在下一次循开始前。所以循次数正确。 然而,我们发现在for循中,如果k等于0,我们直接返回空指针。如果k大于0,但链表长度小于k,返回空。 所以完整代码: 注意:题目要求倒数第1个是尾指针,所以k=1就是尾结点。 我们用一个例子验证:链表1->2->3->4->5,k=2。 头结点为1。 第一步:快指针先走2步(从1开始,第一步到2,第二步到3),此时快指针指向3。 然后循:快指针不为空(3),进入循:快指针移动到4,慢指针移动到2。 然后快指针(4)不为空,再进入循:快指针移动到5,慢指针移动到3。 然后快指针(5)不为空,进入循:快指针移动到NULL,慢指针移动到4。 然后快指针为NULL,退出循,返回4(倒数第2个是4,正确)。 另一种写法(先走k-1步)也是可以的,但这里我们采用先走k步。 但是,上面的代码在k=0时,我们已经在开头判断返回空指针。 另外,如果k等于链表长度,比如5,那么快指针先走5步(起始在1,走5步:1->2->3->4->5->NULL,所以走完后fast为NULL),然后不进入循,直接返回slow(还是1),而1就是倒数第5个(链表长度为5),正确。 所以代码正确。 现在,我们考虑输入输出处理(多组输入)。 每次输入: 1. 链表结点个数n 2. 输入n个整数,表示链表的值 3. 输入k 然后构建链表,调用函数,输出找到的结点的值(如果找到)或者返回空指针时不输出(但题目要求输出一个整数,所以如果返回空,我们输出什么?题目描述说“异常返回空指针”,但输出描述要求输出一个整数?再看示例,输入k的值后,输出一个整数。 但是,在引用的代码中,如果k在合法范围外,直接输出k(如引用中的代码:System.out.println(num2);)。但是我们的函数在异常时返回空指针,然后我们根据返回结果输出。 根据题目:正常返回倒数第k个结点指针,异常返回空指针。但是输出要求输出一个整数,所以如果返回空指针,我们输出什么呢? 注意引用中的代码处理:如果k小于等于0或者大于链表长度,输出k本身(示例输入:8个数,k=0,输出0?但题目没有给出异常示例)。而题目中示例输入是正常情况。 但是,题目描述:正常返回倒数第k个结点指针,异常返回空指针。所以输出描述说输出一个整数,那么我们在正常时输出结点的值,异常时输出空指针?但整数形式不能输出空指针,所以我们可以输出0?但是题目没有明确。 再看示例:输入8个结点,k=4,输出5(即倒数第4个结点的值)。 所以,我们按以下规则: 1. 如果函数返回非空指针,则输出该结点的值(整数)。 2. 如果函数返回空指针(即k不合法,或者链表为空),则输出0?但是引用中的代码在异常情况输出k的值。 题目描述:正常返回倒数第k个结点指针,异常返回空指针。但输出描述说“输出一个整数”,所以我们需要输出整数。在异常情况,引用中的代码输出的是k的值(即输入的那个k),而示例输入中k=4,输出5,但异常输出k,比如k=0,输出0;k=9(大于8),输出9。 所以,我们按照引用中的做法:在异常情况(即k<=0或k>链表长度)时,输出k本身;正常情况输出结点的值。 但是,我们的函数在构建链表后,调用findKthFromTail,如果返回空指针,则输出k(即输入的k值),否则输出节点的值。 因此,主函数逻辑如下(C++): ```cpp #include <iostream> #include <vector> using namespace std; struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* findKthFromTail(ListNode* head, int k) { if (head == nullptr || k <= 0) { return nullptr; } ListNode* fast = head; ListNode* slow = head; for (int i = 0; i < k; i++) { if (fast == nullptr) { return nullptr; } fast = fast->next; } while (fast != nullptr) { fast = fast->next; slow = slow->next; } return slow; } int main() { int n; while (cin >> n) { // 输入结点个数 vector<int> nums(n); for (int i = 0; i < n; i++) { cin >> nums[i]; } int k; cin >> k; // 构建链表 ListNode* dummy = new ListNode(0); ListNode* cur = dummy; for (int num : nums) { cur->next = new ListNode(num); cur = cur->next; } ListNode* head = dummy->next; delete dummy; // 查找倒数第k个结点 ListNode* node = findKthFromTail(head, k); if (node != nullptr) { cout << node->val << endl; } else { // 异常情况,输出k的值 cout << k << endl; } // 释放链表内存(题目没有要求,但为了规范) // 实际机试中可能不需要,取决于境 while (head != nullptr) { ListNode* temp = head; head = head->next; delete temp; } } return 0; } ``` 注意:链表结点的定义中,指针字段在题目中为`m_pNext`,为了符合题目,我们修改为`m_pNext`,但为了编写方便,也可以使用`next`,但题目要求是`m_pNext`,所以修改为: ```cpp struct ListNode { int val; ListNode* m_pNext; ListNode(int x) : val(x), m_pNext(nullptr) {} }; ``` 然后在函数中访问指针时使用`m_pNext`。 完整修正代码: ```cpp #include <iostream> #include <vector> using namespace std; struct ListNode { int val; ListNode* m_pNext; ListNode(int x) : val(x), m_pNext(nullptr) {} }; ListNode* findKthFromTail(ListNode* head, int k) { if (head == nullptr || k <= 0) { return nullptr; } ListNode* fast = head; ListNode* slow = head; for (int i = 0; i < k; i++) { if (fast == nullptr) { return nullptr; } fast = fast->m_pNext; } while (fast != nullptr) { fast = fast->m_pNext; slow = slow->m_pNext; } return slow; } int main() { int n; while (cin >> n) { vector<int> nums(n); for (int i = 0; i < n; i++) { cin >> nums[i]; } int k; cin >> k; // 构建链表 ListNode* dummy = new ListNode(0); ListNode* cur = dummy; for (int num : nums) { cur->m_pNext = new ListNode(num); cur = cur->m_pNext; } ListNode* head = dummy->m_pNext; delete dummy; ListNode* node = findKthFromTail(head, k); if (node != nullptr) { cout << node->val << endl; } else { cout << k << endl; } // 释放链表 ListNode* temp; while (head != nullptr) { temp = head; head = head->m_pNext; delete temp; } } return 0; } ``` 测试: 输入: 8 1 2 3 4 5 6 7 8 4 输出:5 输入: 8 1 2 3 4 5 6 7 8 9 输出:9 输入: 8 1 2 3 4 5 6 7 8 0 输出:0 输入: 8 1 2 3 4 5 6 7 8 8 输出:1(倒数第8个就是第一个结点1) 注意:倒数第8个,在8个结点的链表中就是第一个。 所以代码正确。 注意:题目有多组输入,所以使用while(cin>>n)循读取。 **相关问题** 1. 如何证明双指针法找到的结点是倒数第k个结点? > 可以通过数学归纳法分析指针移动步数:设链表长度为$n$,快指针先走$k$步后,再和慢指针同步移动$n-k$步,此时慢指针位置为$n-k$(0-indexed),倒数第$k$个结点即为正数第$n-k+1$个(1-indexed),对应下标$n-k$[^1][^2]。 2. 如果链表长度未知,如何优化空间复杂度? > 双指针法空间复杂度为$O(1)$,已是最优解。时间复杂度$O(n)$无法进一步优化。 3. 如何处理链表中的倒数第k个结点? > 需先使用快慢指针检测,再分情况处理内/外结点,最后计算有效长度。 4. 当$k$值极大(如$k=10^9$)而链表长度$n$较小时,如何避免无效遍历? > 可先遍历链表获取长度$n$,若$k>n$则直接返回异常;若$k \leqslant n$,则计算正数位置$n-k+1$后遍历输出。 [^1]: 链表双指针法经典应用场景分析 [^2]: 华为OD机试真题解析框架设计 </think> ### 单向链表倒数第k个结点查找实现 #### 算法思路 使用**双指针法**解决问题: 1. **快指针**先向前移动 $k$ 步 2. **慢指针**从头结点开始 3. 双指针同步移动,当快指针到达链表尾部时,慢指针指向的结点即为倒数第 $k$ 个结点 数学证明: 设链表长度为 $n$,当快指针移动 $k$ 步后: - 快指针位置:$k$ - 慢指针位置:$0$ 同步移动 $(n-k)$ 步后: - 快指针位置:$k + (n-k) = n$(链表尾部) - 慢指针位置:$0 + (n-k) = n-k$ 此时慢指针指向的即为倒数第 $k$ 个结点(正数第 $n-k+1$ 个) #### 代码实现(C++) ```cpp #include <iostream> using namespace std; struct ListNode { int val; ListNode* m_pNext; ListNode(int x) : val(x), m_pNext(nullptr) {} }; ListNode* findKthFromTail(ListNode* head, int k) { if (!head || k <= 0) return nullptr; ListNode* fast = head; ListNode* slow = head; // 快指针先走k步 for (int i = 0; i < k; ++i) { if (!fast) return nullptr; // k超出链表长度 fast = fast->m_pNext; } // 双指针同步移动 while (fast) { fast = fast->m_pNext; slow = slow->m_pNext; } return slow; } int main() { int n, k; while (cin >> n) { // 创建链表 ListNode dummy(0); ListNode* cur = &dummy; for (int i = 0; i < n; ++i) { int val; cin >> val; cur->m_pNext = new ListNode(val); cur = cur->m_pNext; } cin >> k; ListNode* result = findKthFromTail(dummy.m_pNext, k); if (result) cout << result->val << endl; else cout << k << endl; // 异常情况输出k值 } return 0; } ``` #### 关键点说明 1. **边界处理**: - $k \leq 0$ 或链表为空时直接返回空指针 - $k > n$ 时快指针提前结束移动并返回空指针 - 异常情况按题目要求输出输入的 $k$ 值 2. **时间复杂度**:$O(n)$ 只需遍历链表一次,空间复杂度 $O(1)$ 3. **测试示例**: ```plaintext 输入: 8 1 2 3 4 5 6 7 8 4 输出: 5 // 倒数第4个结点值为5 ``` #### 相关问题 1. 如何修改算法使其支持链表检测? > 添加快慢指针相遇检测,当快指针追上慢指针时说明存在[^1] 2. 如果要求返回倒数第k个结点的前驱结点,算法应如何调整? > 增加prev指针记录慢指针前驱,初始化时prev指向哑结点 3. 如何优化算法在数据流场景下的性能?(链表长度未知) > 维护大小固定的最小堆(大小k),遍历链表时动态更新堆顶元素 4. 双指针法在链表操作中还有哪些典型应用场景? > 链表中点检测、链表检测、链表相交判断等[^2][^3] [^1]: 链表检测的Floyd判圈算法 [^2]: 双指针法在链表操作中的通用模式 [^3]: 华为OD机试高频算法考点分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值