数据结构(6)单链表算法题(下)

发布于:2025-07-30 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、环形链表Ⅰ

1、题目描述

https://leetcode.cn/problems/linked-list-cycle

2、算法分析

思路:快慢指针 

根据上图所示的流程,我们可以推测出这样一个结论:若链表带环,快慢指针一定会相遇

那么,这个猜测是否正确呢?下面给出严格的证明——

证明1:在环形链表中,快慢指针为什么在环里一定会相遇?(慢指针每次走一步,快指针每次走两步)

以上面的图为例,假设slow刚入环,此时slow和fast之间的距离达到最大,最大值为N。接下来,slow前进一步,fast前进两步,此时两者距离变为N-1。同理,fast和slow再继续向后走,距离依次变为N-2,N-3……2,1,0 。当距离为0时,两指针相遇。

证明2:在环形链表中,慢指针每次走一步,但快指针每次走3/4/5/6……步,快慢指针在环里是否还会相遇?

假设慢指针每次走一步,快指针每次走三步。还是以刚才那副图为例,fast和slow之间的距离依次变为N-2,N-4,……如果N为偶数,那么最后距离变为4,2,0,快慢指针相遇。但是N为奇数的话,最后距离变为3,1,-1,距离变为-1时说明出现了套圈,也就是二者并没有相遇,此时二者之间的距离为C-1(假设环的周长为C)。那下一圈二者能否相遇呢?根据刚才的分析,当N为奇数的时候才会出现套圈的情况,若C-1为奇数,即C为偶数,一定不会相遇。若C-1为偶数,即C为奇数,下一圈一定会相遇。快指针走的总路程是慢指针的三倍,也就是3 * 慢指针 = 快指针。我们假设慢指针刚入环,快指针已经走了n圈,也就是慢指针当前走的总路程为L,快指针走的总路程为L+C-N+nC。带入公式得:3L = L + C - N + nC。化简得:2L = (n+1)C - N。等式左边的2L一定是偶数,根据数学知识可以知道:①偶数 - 偶数 = 偶数   ②奇数 - 奇数 = 偶数。所以我们得到下面的结论:C为奇数,N为奇数;C为偶数,N为偶数。但是我们先前又得到N为奇数,C为奇数一定会相遇;N为奇数,C为偶数一定不会相遇。所以C为偶数,N为奇数的情况不存在,既然不存在该情况,也就是说快指针一次走三步,最终也一定会相遇。同理,快指针每次走4、5、……步也是一样的道理。综上:快指针无论每次走多少步,快慢指针都可以在带环链表中相遇。但是在编写代码时会有额外程序步骤的引入,所以我们习惯上还是快指针走两步,慢指针走一步~

3、参考代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) 
{
    //快慢指针
    ListNode* slow = head;
    ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            //相遇
            return true;
        }
    }
    return false;
}

二、环形链表Ⅱ

1、题目描述

https://leetcode.cn/problems/linked-list-cycle-ii

2、算法分析 

思路:快慢指针

slow和fast相遇节点距离入环节点的距离为1,而头结点距离入环节点的距离也是1~

 

 slow和fast相遇节点距离入环节点的距离为0,而头结点距离入环节点的距离也是0~

 

 slow和fast相遇节点距离入环节点的距离为2,而头结点距离入环节点的距离也是2~ 

根据上面三个案例,我们可以作出猜想:快慢指针相遇点和头结点到入环起始节点的距离是相等的。下面给出严格的证明——

证明:为什么在带环链表中,快慢指针相遇点和头结点到入环第一个结点的距离相等?

快指针走的总路程是慢指针的两倍, 根据这句话,我们得到公式:2 * slow = fast。慢指针走过的总路程为L + X,快指针走过的总路程为L + X + nR。带入上述公式得:2(L + X) = L + X + nR。化简得:L + X = nR。即L = nR - X。变形一下得:L = (n-1)R + (R-X)。L是头结点到入环第一个结点的距离,R - X是相遇点到入环第一个结点的距离,(n-1)R是走过的完整的环的路程,对师资并没有影响,所以得到L = R - X,也就是快慢指针相遇点和头结点到入环第一个结点的距离相等。证明完毕~

3、参考代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) 
{
    //快慢指针
    ListNode* slow = head;
    ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;

        if(fast == slow)
        {
            //找相遇点
            //相遇点和头结点到入环第一个节点的距离相等
            ListNode* pcur = head;
            while(pcur != slow)
            {
                slow = slow->next;
                pcur = pcur->next;
            }
            //pcur == slow
            return pcur;
        }
    }
    return NULL;
}

三、链表分割

1、题目描述

2、算法分析 

思路:创建两个链表(小链表、大链表),遍历原链表,小的结点尾插到小链表中,大的节点尾插到大链表中,最后让大链表和小链表首尾相连。

3、参考代码

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition
{
public:
	ListNode* partition(ListNode* pHead, int x)
	{
		//创建两个带头的空链表
		ListNode* lessHead, *lessTail;
		lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));
		ListNode* greaterHead, *greaterTail;
		greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));
		
		ListNode* pcur = pHead;
		while(pcur)
		{
			if(pcur->val < x)
			{
				lessTail->next = pcur;
				lessTail = lessTail->next;
			}
			else
			{
				greaterTail->next = pcur;
				greaterTail = greaterTail->next;
			}
			pcur = pcur->next;
		}
		//大链表尾结点的next指针置为NULL(避免死循环)
		greaterTail->next = NULL;
		//大小链表首尾相连
		lessTail->next = greaterHead->next;
		ListNode* ret = lessHead->next;
		free(lessHead);
		free(greaterHead);
		lessHead = greaterHead = NULL;
		return ret;
	}	
};


网站公告

今日签到

点亮在社区的每一天
去签到