数据结构-顺序表&链表经典OJ题

文章目录

一、顺序表算法题

1.移除元素

(链接:RemoveElement)这道题需要在原数组上移除与val相同的元素,并使数组中不等于val的k个元素存放在原数组的前k个位置。

①我们可以先尝试创建一个临时数组tmp,再将nums中不等于val的元素存放到tmp中。待遍历完原数组后,再将tmp中的值拷贝到原数组中的前k个位置:

c 复制代码
int* removeElement(int* nums, int numsSize, int val)
{
	int pos = 0;
	int tmp[numsSize];
	int i = 0;
	for (i = 0; i < numsSize; i++)
	{
		if (nums[i] != val)
		{
			tmp[pos++] = nums[i];
		}
	}
	for (i = 0; i < pos; i++)
	{
		nums[i] = tmp[i];
	}
	return pos;
}

但很可惜,这里不支持变长数组,tmp数组创建不了。这个代码的时间复杂度为O(N),空间复杂度为O(N)。这是空间换时间的做法,我们只需理解知道上面的思路即可。

②如果要求时间复杂度为O(N),空间复杂度为O(1)的话,也就是不创建临时数组来解决这里的问题。很简单,由于上面是创建了一个临时数组,我们可以根据上面的思想,直接在原数组上进行:
这个方法其实就是上面第一个方法的简化。不创建临时数组,就在同一个数组中进行遍历和存储不等于val的元素。当遍历完数组后,dst就是nums数组中所有不等于val值的元素的个数:

c 复制代码
int removeElement(int* nums, int numsSize, int val) 
{
	int src = 0;
	int dst = 0;
	while (src < numsSize)
	{
		if (nums[src] != val)
		{
			nums[dst++] = nums[src++];
		}
		else
		{
			src++;
		}
	}
	return dst;
}

2.删除有序数组中的重复项

(链接:RemoveDuplicates)
这道题和移除元素那题很像,我们也是可以利用移除元素那题的思路来做这里的题。在原链表中进行重复元素的删除,并返回删除重复项后的数组的新长度:(注意:这里给你的数组是非严格递增的,也就是不递减的意思)

c 复制代码
int removeDuplicates(int* nums, int numsSize) 
{
	int dst = 0;
	int src = 1;
	while (src<numsSize)
	{
		if (nums[dst] == nums[src])
		{
			src++;
		}
		else
		{
			nums[++dst] = nums[src++];
		}
	}
	return dst + 1;
}

此方法其实就是在一个数组上同时进行遍历比较和存储元素而已。需要画图仔细理解上面方法的过程。

3.合并两个有序数组

(链接:MergeSortedArray)
这题意思是将nums2数组的元素合并到nums1数组中,使得合并后的nums1数组也同样按非递减顺序排列。原来nums1和nums2数组的元素也是按非递减的顺序排列的。

首先要知道一点,题目说把nums2的数据合并到nums1中,说明nums1数组的长度除了它原来存储的数据以外,后面剩下的长度肯定是用来存储nums2数组的元素的。

c 复制代码
void merge(int* nums1,int m, int* nums2, int n) 
{
	int end1 = m - 1;
	int end2 = n - 1;
	int i = m + n - 1;
	while (end1 >= 0 && end2 >= 0)
	{
		if (nums1[end1] > nums2[end2])
		{
			nums1[i--] = nums1[end1--];
		}
		else
		{
			nums1[i--] = nums2[end2--];
		}
	}

	while (end2 >= 0)
	{
		nums1[i--] = nums2[end2--];
	}
}

当end1和end2有一个找完了,即有一个遍历完了数组,则只要end1和end2两个都还没有遍历到数组下标的非法位置,循环就要继续。end1和end2一定有一个先遍历完,所以需要分析一下。

(1) 如果是nums2先遍历完,那就没有问题,因为nums2数组里的元素都已经存到了nums1中。
(2) 如果是num1先遍历完,则说明nums2中还剩有元素没有放到nums1中,所以在出循环后,只需要判断一下end2有没有到达非法位置,没有就要把剩下的元素存到nums1中。

二、链表算法题

1.移除链表元素

(链接:RemoveSListElements)
1.这道题是在原链表上进行删除,删除链表中所有等于val的值,并返回新的头结点。这里需要分情况讨论:
(1) 如果头结点的val就是要删除的val,那么就要涉及头删:
(2) 如果头结点的val不等于要删除的val,就要往后遍历链表:

c 复制代码
struct ListNode* removeElements(struct ListNode* head, int val) 
{
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    while(cur)
    {
        if(cur->val == val)
        {
            if(prev == NULL)
            {
                cur = head->next;
                free(head);
                head = cur;

            }
            else
            {
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}

这里需要考虑头删的情况,当头结点的val不等于要删除的val,则cur指针就会开始往后遍历,此时才会有prev指针记录它的前一个节点。所以只要prev还没赋值就说明还在头删,prev开始记录cur的前一个节点了说明头节点的val就不是要删除的val。

2.当然这道题还有更简单的方法,可以先创建两个指针newHead和newTail,分别用来指向原链表中值不等于val的节点组成的新链表的头和尾:

c 复制代码
struct ListNode* removeElements(struct ListNode* head, int val) 
{
    struct ListNode* newHead;
    struct ListNode* newTail;
    newHead = newTail = NULL;
    struct ListNode* cur = head;
    while(cur)
    {
        if(cur->val != val)
        {
            if(newHead == NULL)
            {
                newHead = newTail = cur;
            }
            else
            {
                newTail->next = cur;
                newTail = newTail->next;
            }
        }
        cur = cur->next;
    }
    if(newTail && newTail->next)
        newTail->next = NULL;
    return newHead;
}

2.反转链表

(链接:ReverseSList)
反转链表是单链表中的经典OJ题。思路是遍历链表,让头结点指向反过来。

c 复制代码
struct ListNode* reverseList(struct ListNode* head) 
{
    struct ListNode* rhead = NULL;
    struct ListNode* cur = head;
   
    while(cur)
    {
        struct ListNode* next = cur->next;
        cur->next = rhead;
        rhead = cur;
        cur = next;
    }
    return rhead;
}

3.链表的中间节点

(链接:MiddleOfSList)
这道题就有一个简单的方法就是快慢指针: 🍉 🍉定义两个指针分别是慢指针slow和快指针fast;慢指针一次移动一步,而快指针一次移动两步。这样快指针移动的步数就是慢指针的两倍,当快指针遍历完链表时,慢指针指向的就是链表的中间节点:

c 复制代码
struct ListNode* middleNode(struct ListNode* head) 
{
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while( fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

注意:循环的条件fast && fast->next不能交换位置,因为并不知道会给奇数个的链表还是偶数个的链表,所以如果给偶数个的节点,那fast走到尾就是空指针,不能对其进行解引用。

4.合并两个有序链表

(链接:MergeTwoSortedSList)

两个有序链表的合并,可以定义两个指针list1和list2分别指向两个待合并的链表的头结点,然后依次往后遍历比较两个链表头结点的大小,让小的尾插到新链表中:
当然只要涉及到指针的解引用就要提前判断指针是否为空,即这里会不会出现空链表:

c 复制代码
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1 == NULL)
        return list2;
    if(list2 == NULL)
        return list1;

    struct ListNode* head = NULL,*tail=NULL;
    while(list1 != NULL && list2 != NULL)
    {
        if(list1->val < list2->val)
        {
            if(tail == NULL)
            {
                head = tail = list1;
            }
            else
            {
                tail->next = list1;
                tail = tail->next;
            }
            list1 = list1->next;
        }
        else
        {
            if(tail == NULL)
            {
                head = tail = list2;
            }
            else
            {
                tail->next = list2;
                tail = tail->next;
            }
            list2 = list2->next;
        }
    }
    if(list1 == NULL)
        tail->next = list2;
    if(list2 == NULL)
        tail->next = list1;

    return head;
}

注意:list1和list2一定有一个会先遍历完,所出了循环以后一定判断那个指针还没走到空,没走到空就说明这个指针指向的链表还有节点没有尾插到新链表中,要记得把剩下的节点尾插过来。

5.链表分割

(链接:SListSegment)
这题的难度偏困难,需要理解题目的意思:
比如x等于5,那意思就是把链表中所有小于5的节点排在其余节点之前,且挪动后不能改变链表中元素与元素之间的相对位置。比如原来链表中6是在8、5、7的前面,那新链表中也是要保证6这个节点在8、5、7这几个节点之前,它们之间的相对位置不能改变。可以看到上图中就把小于5的所有节点挪到了其余节点之前。

这题的思路是创建四个指针:smallhead、smalltail、bighead、bigtail。其中smallhead、smalltail是用来指向小于x的节点组成的链表的头尾指针,bighead、bigtail是用来指向大于等于x的节点组成的链表的头尾指针。而且这里会申请哨兵位节点,也就是smallhead和bighead都是哨兵位:

c 复制代码
ListNode* partition(ListNode* pHead, int x)
{
	ListNode* smallphead = NULL;
	ListNode* smalltail = NULL;
	ListNode* bigphead = NULL;
	ListNode* bigtail = NULL;
	
	smallphead = smalltail = (ListNode*)malloc(sizeof(ListNode));
	smallphead->next = NULL;
	bigphead = bigtail = (ListNode*)malloc(sizeof(ListNode));
	bigphead->next = NULL;
	
	ListNode* cur = pHead;
	while (cur)
	{
		if (cur->val < x)
		{
			smalltail->next = cur;
			smalltail = smalltail->next;
		}
		else
		{
			bigtail->next = cur;
			bigtail = bigtail->next;
		}
		cur = cur->next;
	}

	smalltail->next = bigphead->next;
	bigtail->next = NULL;
	ListNode* newHead = smallphead->next;
	free(smallphead);
	free(bigphead);
	return newHead;
}

尾插后的样子如下:

在形成新的链表后,记得要将申请的哨兵位节点释放掉,以免造成内存泄漏。

6.链表的回文结构

(链接:PalindromeSList)
回文就是对称的意思,比如:
这道题会用到前面讲过题的思路来进行判断链表是否是回文结构!首先给的链表可能是奇数个节点的,也可能是偶数个节点的。我们可以通过快慢指针来先找到链表的中间节点,然后从中间节点往后我们反转链表,让反转后的链表跟原链表的前半截比较,如果一样就是回文结构。

c 复制代码
//1.找中间节点函数
struct ListNode* middleNode(struct ListNode* head)
{
	struct ListNode* slow = head;
	struct ListNode* fast = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}
//2.反转链表函数
struct ListNode* reverseList(struct ListNode* head)
{
	struct ListNode* rhead = NULL;
	struct ListNode* cur = head;

	while (cur)
	{
		struct ListNode* next = cur->next;
		cur->next = rhead;
		rhead = cur;
		cur = next;
	}
	return rhead;
}
//3.判断链表是否是回文结构
bool chkPalindrome(ListNode* Head) 
{
	struct ListNode* mid = middleNode(Head);
	struct ListNode* midRhead = reverseList(mid);

	struct ListNode* cur = Head;
	while (midRhead)
	{
		if (midRhead->val != cur->val)
		{
			return false;
		}
		midRhead = midRhead->next;
		cur = cur->next;
	}
	return true;
}

注意:题目要求是要时间复杂度为O(N),空间复杂度为O(1),也就是不创建额外的变量或者数组来解决这道题。

7.相交链表

(链接:IntersectionOfTwoSList)
这道题是要找两链表相交的节点,首先我们可以先判断两链表是否相交,直接遍历两个链表,然后比较两个链表的尾结点指针是否相等?如果相等,说明一定相交;如果尾结点指针不相等,说明不相交,则返回空。还要知道一点:相交的节点的next只能指向一个节点:
相交最差的情况就是节点在尾结点处相交:
两个链表没有公共节点,就是不相交:
在判断出相交的情况下,还要返回第一个相交的节点地址。思路是:先定义两个指针shortlist和longlist,分别用来指向短链表的头和长链表的头;然后让长的链表先走差距步,即让长链表的指针longlist往后移动,等两个链表的长度一样长了,再同时往后遍历比较节点的地址,第一次遇到相等地址的节点就是第一个相交的节点。

c 复制代码
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
	struct ListNode* tailA = headA;
	struct ListNode* tailB = headB;
	int lenA = 1;
	int lenB = 1;
	while (tailA->next)
	{
		tailA = tailA->next;
		lenA++;
	}
	while (tailB->next)
	{
		tailB = tailB->next;
		lenB++;
	}
	//判断是否相交
	if (tailA != tailB)
	{
		return NULL;
	}
	int gap = abs(lenA - lenB);
	struct ListNode* shortlist = headA;
	struct ListNode* longlist = headB;
	if (lenA > lenB)
	{
		shortlist = headB;
		longlist = headA;
	}
	//移动差距步
	while (gap--)
	{
		longlist = longlist->next;
	}
    //遍历找第一个公共节点
	while (longlist != shortlist)
	{
		longlist = longlist->next;
		shortlist = shortlist->next;
	}

	return shortlist;
}

由于这道题提示两个链表listA和listB至少是有一个节点的,所以代码中不需要考虑链表为空的情况,头指针可以解引用。

8.环形链表Ⅰ

(链接:CycleListⅰ)
这道题是要判断所给链表是否是带环链表。带环的链表是不能遍历的,因为会造成死循环。比较简单的一个方法就是利用快慢指针来做这道题。链表的带环形式大致有三种:
先分析一下:现在有两个指针slow和fast,slow每次走1步,而fast每次走2步,如果是上图中的第二种带环链表:


这里会有疑惑:为什么fast一定会与slow相遇?看下图的移动过程:
由于fast是每次走2步,而slow是每次走1步;所以在fast追逐slow的过程中,它们之间的距离每移动一次就会缩减1,所以到后面fast一定会和slow相遇。这样通过快慢指针就可以检查链表是否是带环的:

c 复制代码
bool hasCycle(struct ListNode *head)
{
	struct ListNode* slow = head;
	struct ListNode* fast = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;

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

只要是带环链表,通过上面的快慢指针方法,fast和slow就一定会相遇。如果不是带环的,那fast或者fast的next一定会走到空。
(注意:这里的快慢指针是慢指针1次走1步,快指针1次走2步;如果快指针1次走3步甚至3步以上则fast与slow会不会相遇呢?这个需要另外讨论)

9.环形链表Ⅱ

(链接:CycleListⅱ)
这道题就是上面一道题的延续,判断链表是否带环就是上一题的方法。但这里还要返回链表开始入环的第一个节点,如果链表无环就返回NULL。那要怎么找开始入环的第一个节点呢?

⚽⚽方法:让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环移动, 两个指针均是每次走一步, 最终肯定会在入口点的位置相遇。

下面来解释为什么上面的方法就能找到入环的第一个节点:
由于快指针fast1次走2步,慢指针slow是1次走1步;则fast走的路程是slow的两倍,则当fast与slow第一次相遇时,fast走的路程与slow走的路程关系为:
这里需要知道:在slow指针入环后,在第一圈内fast指针就会追上slow指针,并与slow指针相遇。因为fast指针每次移动的步数是slow指针的两倍,所以在第一圈内fast必追上slow指针。试想:如果slow指针都沿着环形表转了一圈了,那fast指针肯定都转了不止一圈了,所以在一圈内fast必然追上并相遇于slow指针。

但要说的是上面的推导公式是错的!!!上面只是一种特例,如果链表是下图的样子:

很明显入环前的一段链表的长度肯定大于C-X,说明我们被上面的图像误导了,以为入环前的长度L就等于C-X的长度。单向链表的长度L及环链表的周长C都可以有大有小,上面只是其中一种。我们要从特殊推导到一般:

当入环前的一段链表的长度相较于环链表的周长比较长时,说明在slow到达入环点时,fast指针已经在环链表里面转了不止一圈了,所以L真正的值应该为:

n是slow入环前,fast在环里转的圈数。fast沿环链表转的圈数n的多少取决于L和环链表的周长C。但毋庸置疑的是当slow入环以后,fast以每次2步的移动速度一定会追上slow,并与slow相遇。所以解释了为什么一个指针从起始位置开始走,另一个从判环相遇的位置开始走,最终会在入环点相遇。

c 复制代码
struct ListNode *detectCycle(struct ListNode *head) 
{
	struct ListNode* slow = head;
	struct ListNode* fast = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;

		if (slow == fast)
		{
			struct ListNode* meet = slow;
			while (head != meet)
			{
				head = head->next;
				meet = meet->next;
			}
			return meet;
		}
	}
	return NULL;
}

10.随机链表的复制

(链接:CopyListWithRandomPointer)
题目是要让我们拷贝一个跟下图一样的链表:
这题在单链表的基础上,还在每个节点中增加了一个随机指针random,这个指针是随机地指向链表中的其他节点,甚至是空指针。问题是我们是要拷贝此链表,就是要申请新的节点来存储上面的节点的值,且拷贝节点的指针指向要跟原链表里的一样。

该链表的val和next指针是很容易拷贝的,唯独random指针的指向不好解决,此题如果要求时间复杂度为O(N)的话,那在C语言阶段最优的做法如下:
🍎1.将拷贝的节点尾插到原节点的后面
🍎2.让拷贝节点的random指向原节点random的next:
copy->next = src->random->next(核心)
🍎3.从原链表中解下拷贝的节点并使之连接成单链表,还原原链表

步骤1:让拷贝的节点尾插到原节点的后面。即让原节点的next指向拷贝节点,且拷贝节点的next指向原节点的下一个节点:
步骤2:从头开始遍历,让拷贝节点的random指向原节点random的next:
步骤3:将拷贝节点从原链表上解下来,并连成与原来链表一样的拷贝链表,最后恢复原链表:

c 复制代码
struct Node* copyRandomList(struct Node* head)
{
	//1.让拷贝节点尾插到原节点的后面
	struct Node* cur = head;
	while (cur)
	{
		struct Node* node = (struct Node*)malloc(sizeof(struct Node));
		node->val = cur->val;
		struct Node* next = cur->next;
		cur->next = node;
		node->next = next;

		cur = next;
	}
	//2.让拷贝节点的random指向原节点random的next
	cur = head;
	while (cur)
	{
		struct Node* copy = cur->next;
		struct Node* next = copy->next;

		if (cur->random == NULL)
		{
			copy->random = NULL;
		}
		else
		{
			copy->random = cur->random->next;
		}
		cur = next;
	}
	//3.将拷贝节点从原链表上解下来组成拷贝链表,恢复原链表
	struct Node* copyHead = NULL;
	struct Node* copyTail = NULL;
	cur = head;
	while (cur)
	{
		struct Node* copy = cur->next;
		struct Node* next = copy->next;
		if (copyTail == NULL)
		{
			copyHead = copyTail = copy;
		}
		else
		{
			copyTail->next = copy;
			copyTail = copyTail->next;
		}
		//还原链表
		cur->next = next;
		cur = next;
	}
	return copyHead;
}

主要是理解做这题的思路方法。这道题是偏困难的。

三、环形链表的约瑟夫问题

(链接:JosephCycleList)
著名的Josephus问题
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人,该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
了解了约瑟夫环是什么以后,我们通过将n个人编号1~n,将值存放在一个环链表中,则需要申请节点并使之成环。以上面实例的5个人为例,从第1个人开始报数,数到2的人要自杀,那就相当释放链表中存放对应编号的节点。虽然是环状链表,但是在环中是单向的,所以我们需要知道释放节点的前一个节点prev,让prev指向释放节点的下一个节点,再释放节点;这样就相当于从n个人围成的圈中去掉了自杀的人:

1.约瑟夫环图示理解

释放了计数到2的节点后,紧接着就迭代,让现在的pcur重新从1开始计数,重复上面的步骤。直到环形链表中只剩下1个节点为止,那剩下节点的val就是最后留下的人的编号:

c 复制代码
typedef struct ListNode ListNode;
//申请节点
ListNode* BuyNode(int x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		exit(1);
	}
	node->val = x;
	node->next = NULL;

	return node;
}
//为n个值创建一个环链表,并返回尾指针
ListNode* CycleList(int n)
{
	ListNode* phead = NULL;
	ListNode* ptail = NULL;
	for (int i = 1; i <= n; i++)
	{
		ListNode* node = BuyNode(i);
		if (ptail == NULL)
		{
			phead = ptail = node;
		}
		else
		{
			ptail->next = node;
			ptail = ptail->next;
		}
	}
	ptail->next = phead;

	return ptail;
}
//约瑟夫环的代码,最后返回的是环链表剩下节点的值val
int ysf(int n, int m) 
{
	ListNode* prev = CycleList(n);
	ListNode* pcur = prev->next;
	int count = 1;
	while (pcur->next != pcur)
	{
		if (count == m)
		{
			prev->next = pcur->next;
			free(pcur);
			pcur = prev->next;
			count = 1;
		}
		else
		{
			prev = pcur;
			pcur = pcur->next;
			count++;
		}
	}

	return pcur->val;
}

四、快慢指针相遇问题

1.环形链表Ⅰ(补充)

在环形链表Ⅰ问题中:我们讲解过如果slow指针1次走1步,fast指针1次走两步;那么fast指针与slow指针一定会在环里面相遇,因为fast的速度是slow的2倍,所以在slow入环后,fast就会开始追逐slow指针,它们之间每移动一次距离就会减1,所以fast最终会与slow相遇。
接下来是解答:slow仍然1次走1步,但fast 1次走3/4/5... 那fast会不会与slow相遇呢?首先知道fast不管走3步还是3步以上,fast都比slow快;则fast追上slow是肯定的,问题是追上了会不会相遇?简单分析一下:

step1:
按照上面的分析,慢指针每次走一步,快指针每次走三步,此时快慢指针的最大距离为N,接下来的追逐过程中,每追击一次,他们之间的距离缩小2步追击过程中fast和slow之间的距离变化:

分析:
(1) 如果N是偶数,第一轮就追上了
(2) 如果N是奇数,则第一轮追不上;追上了但错过了,距离变成-1,即C-1,进入新一轮的追击
a.C-1如果是偶数,那么下一轮就追上了
b.C-1如果是奇数,那么就永远都追不上
总结一下追不上的前提条件:N是奇数,C是偶数

step2:
假设:
环的周长为C,头结点到slow结点的长度为L,slow走一步,fast走三步,当slow指针入环后fast指针会在环中开始追逐slow,假设此时slow刚好到达入环点,fast指针已经绕环x周,fast与slow距离为N。则slow所走的路程与fast所走的路程如下:
slow: L
fast: L+xC+C-N
由于慢指针走一步,快指针要走三步,因此得出:3*慢指针路程=快指针路程。即:
3L=L+xC+C-N
2L=(x+1)C-N
对上述公式继续分析:由于偶数乘以任何数都为偶数,因此2L一定为偶数,则可推导出可能的情况:
● 情况1:偶数=偶数-偶数
● 情况2:偶数=奇数-奇数
由step1中(1)得出的结论:如果N是偶数,则第一圈fast就与slow相遇了。由step1中(2)得出的结论:如果N是奇数,则fast指针和slow指针在第一轮追击的时候错过了,开始进行下一轮的追逐; 当N是奇数,要满足以上的公式,则(x+1)C必须也要为奇数,即C为奇数,满足(2)中a的结论,则快慢指针会相遇。

🏀因此,step1中的"N是奇数, C是偶数"不成立,既然不存在该情况,则快指针一次走3步最终也一定会相遇。同样的可以推导出快指针一次走4、5...步最终也会相遇,其证明方式同上。
💡提示:
虽然已经证明了快指针不论走多少步都可以满足在带环链表中相遇,但是在编写代码的时候会有额外的步骤引入,涉及到快慢指针的算法题中通常习惯使用慢指针走一步快指针走两步的方式。

相关推荐
王景程26 分钟前
Java冒泡排序算法之:变种版
java·数据结构·算法
zc.ovo2 小时前
寒假康复训练2 edu111(A-C)
c语言·数据结构·算法
雾月553 小时前
LeetCode1170 比较字符串最小字母出现频次
数据结构·算法
m0_dawn3 小时前
算法竞赛(蓝桥杯)贪心算法1——数塔问题
数据结构·python·算法·职场和发展·蓝桥杯
张小小大智慧5 小时前
排序算法(C语言版)
数据结构·算法·排序算法
Ning_.5 小时前
力扣第 54 题: 螺旋矩阵
数据结构·算法·leetcode
m0_dawn6 小时前
算法(蓝桥杯)贪心算法4——拦截导弹的系统数量求解
数据结构·python·算法·职场和发展·蓝桥杯
多多*8 小时前
双端队列实战 实现滑动窗口 用LinkedList的基类双端队列Deque实现 洛谷[P1886]
java·开发语言·jvm·数据结构·redis·算法
Ly.Leo8 小时前
什么是报文的大端和小端,有没有什么记忆口诀?
数据结构
墨楠。9 小时前
数据结构学习记录-数据结构概念
数据结构·学习