图解:如何旋转链表

tech2022-08-15  140

前言

上期,小莱给大家分享了利用双指针定位删除节点及尾节点位置的方法(见《图解:删除链表倒数第N个节点》)。这期,小莱继续给大家分享一道利用双指针处理的链表拓展题。

题目:

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入:1->2->3->4->5->NULL, k = 2

输出:4->5->1->2->3->NULL

解释:

向右旋转 1 步:5->1->2->3->4->NULL

向右旋转 2 步:4->5->1->2->3->NULL

示例 2:

输入:0->1->2->NULL, k = 4

输出:2->0->1->NULL

解释:

向右旋转 1 步:2->0->1->NULL

向右旋转 2 步:1->2->0->NULL

向右旋转 3 步:0->1->2->NULL

向右旋转 4 步:2->0->1->NULL 

画外音:Leetcode第61题,中等/难度,通过率40.6%  !!!

先分段后连接

下图为链表分别向右旋转2、3、4个位置后的情况。

经过观察,我们不难发现:

k=2时, 链表从位置3节点后分成两部分;

k=3时, 链表从位置2节点后分成两部分;

k=4时, 链表从位置1节点后分成两部分。

然后尾节点指向头节点就可以实现链表的旋转。

那么,这道看似旋转的题就很简单了,我们可以将其分为三步:

找到分段节点位置;

从分段节点后断开;

将尾节点指向头节点。

头节点位置是已知的,分段节点位置(位置3/位置2等)如何获取呢?

根据初始状态图:

位置3节点指向倒数第k=2个节点;

位置2节点指向倒数第k=3个节点;

位置1节点指向倒数第k=4个节点。

《图解:删除链表倒数第N个节点》里提到的前后双指针方法在这里就可以派上用场了。

画外音:为了与Leetcode保持一致,在这里没有使用单链表基础知识里实现的链表结构(主要区别在哨兵节点)。

可能有人发现了,这里的k值都是小于链表长度的。那么当k大于等于链表长度时,如何定位分段节点位置呢?

 

由上图可以看出:

当k为链表长度的倍数时,链表是维持原状的。当k为非倍数时,分段节点位置为len-(k%len),指向倒数第k%len个节点。

那么问题就迎刃而解了!!!

代码实现:

struct ListNode* rotateRight(struct ListNode* head, int k){     if (head == NULL || head->next == NULL || k == 0) {         return head;     }     struct ListNode* p,*q,*newHead,*tmp = head;     int len,i;     len = i = 0;     // 求出链表长度     while (tmp != NULL) {         tmp = tmp->next;         len++;     }     k = k % len;     // k为链表长度倍数时旋转后还是原位置     if (k == 0) {         return head;     }     p = q = (struct ListNode*)malloc(sizeof(struct ListNode));     p->next = head;     q->next = head;     while(i < k) {         q = q -> next;         i++;     }     while (q->next != NULL) {         q = q -> next;         p = p -> next;     }     newHead = p->next;     p->next = NULL;     q->next = head;     return newHead; }

先连接后分段

既然最终会将头节点指向尾节点,我们能不能一开始就把链表首尾相连,找到分段位置,将其断开呢?

那么我们的操作顺序就变为了:

链表首尾相连;

找到分段位置;

从分段节点后断开。

代码实现:

struct ListNode* rotateRight(struct ListNode* head, int k){     if (head == NULL || head->next == NULL) {         return head;     }     struct ListNode *q,*p = head;     int len = 1;     // 求出链表长度     while (p->next != NULL) {         p = p->next;         len++;     }     p->next = head;   // 链表首尾相连成环     p = head;     k = len - (k % len);     while (k > 0 && p != NULL) {         q = p;          // 新尾节点         p = p->next;    // 新头结点         k--;     }     q->next = NULL;     return p; }

其实,这种方式与上边的方法在本质上是没有区别的,只是将首尾相连的操作前置了。不过可以作为一种解题思路。

总结

1、本文中介绍了两种旋转链表的方式:

先分段后连接:将链表先从分段节点后分割开,然后首尾连接起来;

先连接后分段:将链表先首尾连接起来,再从分段节点后分割开。

2、分段节点位置计算方式为len-(k % len),所指向节点位置计算方式为k%len。

少侠留步

看你骨骼清奇,天赋异禀!

送你一份大厂面试秘籍!

关注公众号「IT界农民工」回复「面试」即可获取!

关于作者

作者:大家好,我是莱乌,BAT搬砖工一枚。从小公司进入大厂,一路走来收获良多,想将这些经验分享给有需要的人,因此创建了公众号「IT界农民工」。定时更新,希望能帮助到你。

往期推荐:

《图解:链表基础知识及反转》

《图解:链表环的问题》

《图解:删除链表倒数第N个节点》

最新回复(0)