指定区间反转
将上图letfpos=2,rightpos=4,将这两个位置区间反转,也就是蓝色区间的链表反转。
该问题解法也可分为头插和穿针引线
头插法
头插法可以实现链表反转,那么首先要找到插入的头在哪,观察上图可发现,他们都是在node(1)之后进行链表操作的,所以像上图的头结点就可以看做是node(1)。
要记住,所谓头插法,不是一定要在整个链表的头部插入,而是在要反转的链表的头结点(就是首结点的前驱结点)插入。
但是还有可能它就是让从整个链表的第一个结点进行反转,leftpos=1,那么这会虚拟结点就派上用了,它可以让众结点平等,不论是原链表的第一个结点还是其他结点都会被视为统一。
因为虚拟结点就当做此时链表的第一个结点,后面所有结点都不再是第一个结点,就不会有插入在头结点还是中间结点的情况了。
找到该头结点可以用for(i=1; i<=left-1; i++)来找到。
再观察上图可发现,每有一个新结点插入时,node(2)总是在这串反转链表的最后一个,所以node(2)可以一直连在链表中,有个妙招就是让node(2)连在一下要反转的结点的下一个结点,也就是node(2)=node(2)->next->next; 这样可以确保node(2)不会丢失。
除了node(2)要操作(right-left)个元素,所以这个当做循环条件。当操作最后一个要反转的元素时,node(2)可以刚好与原链表接上,这个想法真的很巧妙,拓宽了我的思路。
代码如下:
struct ListNode *reverseBetween1(struct ListNode *head, int left, int right)
{
//虚拟结点
struct ListNode *newhead=(struct ListNode*)malloc(sizeof(struct ListNode));
newhead->next=head;
//p为反转链表的头结点
struct ListNode *p=newhead;
for(int i=1;i<=left-1;i++)
p=p->next;
//node1为要与原链表相连的结点,该结点一直在反转链表的末尾
struct ListNode *node1=p->next;
for(int i=1;i<=right-left;i++)
{
//node2为需要操作的结点
struct ListNode *node2=node1->next;
//操作之前先将node1连在链表上,也就是下下个结点
node1->next=node2->next;
//操作node2,头插法node2
node2->next=p->next;
p->next=node2;
}
return newhead->next;
}
穿针引线法
穿针引线法则不依赖结点实现链表反转
由于没有依赖别的结点,所以需要先将要反转的链表单独取下来,然后穿针引线法实现反转。
反转后,将反转的链表与原链表相连,所以这里我们需要先记录一下要连接的前驱pre与后继succ。
之后指针连接,pre->next=right; left->next=succ;
struct ListNode *reverseBetween2(struct ListNode *head, int left, int right)
{
//同样虚拟结点,众生平等
struct ListNode *dummyNode = (struct ListNode *)malloc(sizeof(struct ListNode));
dummyNode->next = head;
struct ListNode *pre = dummyNode;
//找到前驱
for (int i = 1; i <= left - 1; i++)
{
pre = pre->next;
}
//定义右节点
struct ListNode *rightNode = pre;
//找到右节点
for (int i = 1; i <= right - left + 1; i++)
{
rightNode = rightNode->next;
}
//左结点
struct ListNode *leftNode = pre->next;
//后继结点
struct ListNode *succ = rightNode->next;
//取出区间链表
pre->next = NULL;
rightNode->next = NULL;
//穿针引线法反转链表
reverseLinkedList(leftNode);
//将反转链表与链表接上
pre->next = rightNode;
leftNode->next = succ;
return dummyNode->next;
}
两两交换链表结点
同样先创建虚拟结点,众点平等
两两交换相当于反转两个结点,头插法方便一些。直接头结点连到第二个结点 demmy->next=node2,第一个结点连到第二个结点的后继node1->next=node2-->next,第二个结点连第一个结点node2->next=node1
注意顺序不能反,如果先node2->next=node1; node2的后继就会改变为node1,node1就无法连到node2的后继
当连完后,要记得记录下一组的头结点即node1
当然可以看做循环n组反转2个元素,那么循环条件为 node->next!=NULL&&node->next->next!=NULL,后一个条件是防止只剩一个元素,剩一个元素就不用反转了
代码如下:
struct ListNode * swapPairs(struct ListNode * head)
{
struct ListNode *dummyHead = (struct ListNode *)malloc(sizeof(struct ListNode));
dummyHead->next = head;
//temp为反转时的头结点
struct ListNode *temp = dummyHead;
//反转元素
while (temp->next != NULL && temp->next->next != NULL)
{
//要反转的两个结点
struct ListNode *node1 = temp->next;
struct ListNode *node2 = temp->next->next;
//反转
temp->next = node2;
node1->next = node2->next;
node2->next = node1;
//更新头结点
temp = node1;
}
return dummyHead->next;
}