链表的反转
LeetCode的刷题有一道链表的反转,操作要求如下:
代码如下:
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* cur = head;
struct ListNode* prev = NULL;
while(cur)
{
struct ListNode* next = cur->next; //将cur的下一个地址记录起来
cur->next = prev; //将当前cur里面的指向改为指向前一个
prev = cur; //将前一个指针置为当前
cur = next; //将当前指针置为下一个
}
return prev;
}
解题思路:反转就是让原链表的头指向空,然后让下一个地址全部指向前一个地址,但是链表的单向性,如果我们只是单纯的将下一个地址指向前一个地址,那么在下一个地址我们就找不到,所以我们可以事先将下一个地址存起来,所以这里我们用到三个指针,分别记录当前、前一个和下一个来解决这道题。
链表的中间节点
同样是来自LeetCode的一道题,操作要求如下:
代码如下:
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
fast = fast->next->next; //fast一次走两步
slow = slow->next; //slow一次走一步
}
return slow;
}
这题可以有两个解法,一种是暴力遍历,创建一个计数变量i,遍历整个链表有多少元素,然后将结果除以2即为中间节点的位置,。第二种就是比较巧妙的方法,通过申请一对快慢指针,快指针每次走两步,而慢指针一次走一步,这样当快指针走到尾时,慢指针就刚刚好在中间。不过这题有一个地方是需要注意的,如果我将while里面的条件改成(fast->next && fast)可不可以呢?大家可以思考一下并在评论区讨论哦😄
回文链表
同样是来自LeetCode的一道题,操作要求如下:
代码如下:
struct ListNode* ReverseList(struct ListNode* head)
{
struct ListNode* cur = head;
struct ListNode* prev = NULL;
while(cur)
{
struct ListNode* next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
struct ListNode* FindListMid(struct ListNode* head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
bool isPalindrome(struct ListNode* head){
if(head->next == NULL)
return true;
struct ListNode* cur = head;
//先找中间将链表分割为两部分
struct ListNode* mid = FindListMid(head);
//将后半部分逆置
struct ListNode* Rhead = ReverseList(mid);
//将前半部分和后半部分进行判断
while(Rhead && cur)
{
if(cur->val != Rhead->val)
{
return false;
}
else
{
cur = cur->next;
Rhead = Rhead->next;
}
}
return true;
}
这道题目的思路就结合了上两道题的思路了,首先我们先获取链表的中间节点,然后根据中间节点将后半段链表进行反转,反转之后在跟前半段进行比较即可。
相交链表
要求如下:
代码如下:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode* curA = headA;
struct ListNode* curB = headB;
int countA = 0;
int countB = 0;
//先判断两个链表是否相交,根据他们的尾巴是否相同
while(curA->next)
{
curA = curA->next;
countA++;
}
while(curB->next)
{
curB = curB->next;
countB++;
}
//若尾巴相同,证明相交,接着找出第一个节点
if(curA == curB)
{
curA = headA;
curB = headB;
//根据长度判断,链表长的先走相差步
if(countA > countB)
{
int dec = countA-countB;
while(dec--)
{
curA = curA->next;
}
}
else
{
int dec = countB-countA;
while(dec--)
{
curB = curB->next;
}
}
//走完相差步后同步走,当两个链表地址相同时,则为交点
while(curA != curB)
{
curA = curA->next;
curB = curB->next;
}
return curA;
}
return NULL;
}
首先我们可以将这道题拆分为两步,判断两个链表是否相交,若相交再找交点,那么判断是否相交的依据就是判断他们的尾巴是否相同,所以使用遍历判断尾巴是否相同,接下来就是找交点,在这里交点的思路是通过让长的链表先走相差的长度,然后让两个链表在一起走,第一个相同的位置即为我们的交点,借助图片会更好理解:
这里是B会长一点,那么B先走一步,然后在跟A一起走,他们相遇的第一个点即地址相同的点就是我们的交点。
环形链表
要求如下:
代码如下:
bool hasCycle(struct ListNode *head) {
//通过定义快慢指针来判断链表是否带环
struct ListNode *fast = head;
struct ListNode *slow = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
return true;
}
return false;
}
环形链表的思路比较简单,通过定义快慢指针,一个走两步,一个走一步,当快指针能够等于慢指针的时候则证明有环,不能则无环,在这一块有一个思考,如果有环,快指针走两步一定能追到慢指针吗?如果快指针走的是3步,4步或者N步(N>2)能追上吗?
第一个问题的答案是一定,用下图来方便理解:
假设当fast和slow都进环了,那么此时fast开始追slow,假定他们之间的距离为N,那么根据步长fast每次走两步,slow每次走一步,每走一次他们之间的距离就会缩短1,所以距离会变成N-1,N-2,N-3,…,1,0,最终是一定会追上的。
第二个问题相同的思路,用3步来举例子,每走一次他们之间的距离就会缩短2,所以距离会变成N-2,N-4,N-6,…,最终的情况就要根据环的长度来决定了,如果环的长度刚好是偶数则会追上,如果是奇数则一定追不上,所以走N步(N>2)的情况则是不一定会追上。