我们通过头插来实现
将链表上的节点取下来(取的时候需要记录下一个节点),形成新的链表,对新的链表进行头插。
- /**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * struct ListNode *next;
- * };
- */
-
-
- struct ListNode* reverseList(struct ListNode* head)
- {
- //用cur对原链表进行遍历
- struct ListNode* cur=head,*newhead=NULL;
- //原链表为空,遍历结束
- while(cur)
- {
- //记录cur的下一个节点
- struct ListNode* next=cur->next;
- //cur链接到新链表
- cur->next=newhead;
- //cur成为新链表的头指针
- newhead=cur;
- //cur通过next在原链表中向后移
- cur=next;
- }
- return newhead;
- }
这里需要引用哨兵位,先介绍一下
哨兵位就是不带数据的头节点,且为固定的节点,当链表为空时,带哨兵位的链表(右)存在一个头节点(空间),而不带哨兵位的链表(左)则没有节点。
更改带哨兵位的链表(增删查改)就不需要判断,通过二重指针改变头指针,通过 next 就能直接实现。
使用的时候为哨兵位申请一块动态内存,作为头节点,结束的时候可以根据题目要求将其释放。
实现:
- /**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * struct ListNode *next;
- * };
- */
- 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;
- //带一个哨兵位,方便尾插
- head=tail=(struct ListNode*)malloc(sizeof(struct ListNode));
- while(list1&&list2)
- {
- if(list1->val
val) - {
- tail->next=list1;
- tail=tail->next;
- list1=list1->next;
- }
- else
- {
- tail->next=list2;
- tail=tail->next;
- list2=list2->next;
- }
- }
- //
- if(list2)
- {
- tail->next=list2;
- }
- if(list1)
- {
- tail->next=list1;
- }
- //哨兵位的头使用完需要释放掉
- struct ListNode* del=head;
- head=head->next;
- free(del);
-
- return head;
- }
思路是创建两个链表,将小于 x 的尾插到第一个链表,大于 x 尾插到第二个链表。
此题目用带哨兵位的链表做更加简单,因为尾插时不用考虑链表是否为空的情况,将两个链表链接的时候也不需要考虑其中一个链表为空的情况了(即不用担心链表为空的情况)。
- /*
- struct ListNode {
- int val;
- struct ListNode *next;
- ListNode(int x) : val(x), next(NULL) {}
- };*/
- #include
- class Partition {
- public:
- ListNode* partition(ListNode* pHead, int x) {
- struct ListNode* ghead, *gtail, *lhead, *ltail;
- ghead = gtail = (struct ListNode*)malloc(sizeof(struct ListNode));
- lhead = ltail = (struct ListNode*)malloc(sizeof(struct ListNode));
- //用cur遍历
- struct ListNode* cur = pHead;
- while (cur)
- {
- if(cur->val < x)
- {
- ltail->next = cur;
- ltail = ltail->next;
- }
- else
- {
- gtail->next = cur;
- gtail = gtail->next;
- }
- cur = cur->next;
-
- }
- //将低链表的尾链接到高链表的哨兵位之后的节点
- ltail->next = ghead->next;
- //将目标链表的尾置空,否则产生环
- gtail->next = nullptr;
- //拷贝目标链表的头节点
- struct ListNode* head = lhead->next;
- //释放哨兵位
- free(lhead);
- free(ghead);
- return head;
- }
- };
链表的回文结构_牛客题霸_牛客网 (nowcoder.com)
回文结构就是对称的意思,例如 1 2 2 1,1 2 3 2 1。
结合前面的 oj 题目,我们容易想到一个方法,先找出链表的后半段,然后将其逆置,再将其与前半段比较,如果都相同,则为回文结构。
- /*
- struct ListNode {
- int val;
- struct ListNode *next;
- ListNode(int x) : val(x), next(NULL) {}
- };*/
- class PalindromeList {
- public:
- //反转链表
- struct ListNode* reverseList(struct ListNode* head)
- {
- //用cur对原链表进行遍历
- struct ListNode* cur=head,*newhead=nullptr;
- //原链表为空,遍历结束
- while(cur)
- {
- //记录cur的下一个节点
- struct ListNode* next=cur->next;
- //cur链接到新链表
- cur->next=newhead;
- //cur成为新链表的头指针
- newhead=cur;
- //cur通过next在原链表中向后移
- cur=next;
- }
- return newhead;
- }
- //找出中间节点
- struct ListNode* middleNode(struct ListNode* head){
- struct ListNode* slow=head,*fast=head;
- while(fast&&fast->next)
- {
- slow=slow->next;
- fast=fast->next->next;
- }
- return slow;
- }
- bool chkPalindrome(ListNode* head) {
- //找出后半段
- struct ListNode* mid=middleNode(head);
- //将后半段逆置
- struct ListNode* rmid=reverseList(mid);
- while(rmid&&head)
- {
- if(rmid->val!=head->val)
- {
- return false;
- }
- rmid=rmid->next;
- head=head->next;
- }
- return true;
- }
- };
思路:
1.遍历计算出A,B链表各自的长度 lenA,lenB
2.长的链表走差距步 lenA-lenB,此时两条链表的长度相同
3.同时移动找交点(指针相同),最后返回这个交点。
- /**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * struct ListNode *next;
- * };
- */
- struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
- struct ListNode* curA=headA,*curB=headB;
- int lenA=1,lenB=1;
- //提示了链表不为空,因此结尾用下一个节点判断,同时初始长度为1
- while(curA->next)
- {
- curA=curA->next;
- lenA++;
- }
- while(curB->next)
- {
- curB=curB->next;
- lenB++;
- }
- //尾节点不同,一定没有交点
- if(curA !=curB)
- {
- return NULL;
- }
- struct ListNode* longList=headA,*shortList=headB;
- if(lenA
- {
- longList=headB;
- shortList=headA;
- }
- //算出差距步(绝对值)
- int gap=abs(lenA-lenB);
- //长的先走差距步
- while(gap--)
- {
- longList=longList->next;
- }
- //同时走找交点,相等就找到了
- while(longList !=shortList)
- {
- longList=longList->next;
- shortList=shortList->next;
- }
- return longList;
- }
链表有循环或者非循环
循环链表的尾节点指向头节点。
还有一种带环链表,它的尾节点可以指向链表的任意一个节点(包括自己)。
带环链表中的节点会重复出现,我们依然定义一快一慢指针,如果是带环链表,那么快指针一定能追上慢指针,两个指针一定有相等的时候(追及问题);如果不带环,直接就遍历到空了。
- /**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * struct ListNode *next;
- * };
- */
- bool hasCycle(struct ListNode *head) {
- struct ListNode* fast=head,*slow=head;
- //链表可能不为环形
- while(fast&&fast->next)
- {
- fast=fast->next->next;
- slow=slow->next;
- if(slow==fast)
- {
- return true;
- }
- }
- return false;
- }
由带环追及问题引发的思考:
1.slow走一步,fast走两步,一定能追上吗,会不会错过?
2.slow走一步,fast走n步(n>=3),一定能追上吗,会不会错过?
如果 slow 走一步,fast 走三步,假设 slow 入环时,slow 和 fast 的距离为 M,每移动(追击)一次,距离缩小2,若 M 为偶数,则当距离减为0时,刚好追上;若 M 为奇数,距离最小时为 -1,即fast 超过了 slow 一步,此时又要观察环的长度C,此时 slow 和 fast 的距离为 C-1,若 C 为奇数,C-1为偶数,那么再经过一轮追击之后就能刚好追上。如果 C 为偶数,那么 C-1 为奇数,奇数-2 永远为奇数,就永远追不上了。
分析:
设七点到入口长度:L
环的周长:C
入口点到相遇点的距离:X
fast 走的距离(速度)为 slow 的二倍
slow 进环后的一圈内,fast 一定追上 slow,slow 走的距离为 L+X
slow 进环时,fast 已经走了 n(n>=1) 圈了,fast 走的距离为 L+n*C+X
fast 追赶 slow 之前会先补足 n 圈,
fast 走的距离(速度)为 slow 的二倍可知
2(L+X)=L+n*C+X
同减去L+X
L+X=n*C
L=n*C-X
计算的时候我们默认为 fast 已经跑到 n-1 圈,因此不需要关注 n .
从这个结论我们可以得到从相遇点到环入口点的距离与从起点到入口点的距离相同,要想找到入口点,就需要两个指针分别从这两个点开始跑,它们相遇的位置就是入口点。
- /**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * struct ListNode *next;
- * };
- */
- struct ListNode *detectCycle(struct ListNode *head) {
- struct ListNode* fast,*slow;
- fast=slow=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;
- }
- }
- //fast或fast的下一个节点为空,说明无环
- return NULL;
- }
法2:
将环从相遇点断开,相遇点之后作为一条新的链表,和旧链表一起找相交点,相交点就是入口点。