我们通过头插来实现
将链表上的节点取下来(取的时候需要记录下一个节点),形成新的链表,对新的链表进行头插。
cpp
/**
* 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 就能直接实现。
使用的时候为哨兵位申请一块动态内存,作为头节点,结束的时候可以根据题目要求将其释放。
实现:
cpp
/**
* 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<list2->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 尾插到第二个链表。
此题目用带哨兵位的链表做更加简单,因为尾插时不用考虑链表是否为空的情况,将两个链表链接的时候也不需要考虑其中一个链表为空的情况了(即不用担心链表为空的情况)。
cpp
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstddef>
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 题目,我们容易想到一个方法,先找出链表的后半段,然后将其逆置,再将其与前半段比较,如果都相同,则为回文结构。
cpp
/*
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.同时移动找交点(指针相同),最后返回这个交点。
cpp
/**
* 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<lenB)
{
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;
}
链表有循环或者非循环
循环链表的尾节点指向头节点。
还有一种带环链表,它的尾节点可以指向链表的任意一个节点(包括自己)。
带环链表中的节点会重复出现,我们依然定义一快一慢指针,如果是带环链表,那么快指针一定能追上慢指针,两个指针一定有相等的时候(追及问题);如果不带环,直接就遍历到空了。
cpp
/**
* 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 .
从这个结论我们可以得到从相遇点到环入口点的距离 与从起点到入口点的距离相同,要想找到入口点,就需要两个指针分别从这两个点开始跑,它们相遇的位置就是入口点。
cpp
/**
* 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:
将环从相遇点断开,相遇点之后作为一条新的链表,和旧链表一起找相交点,相交点就是入口点。