61. 旋转链表
给定一个链表,旋转链表,将链表每个节点向右移动 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
思路
像这种双指针的链表题目,处理一些细节处理,难点就只有一个:两指针之间的步数差。所以,首指针先走几步呢?
有两种思路,一种是首指针要停在最后一个非空子节点,停止条件是while (first && first->next),一种是首指针要停在链表的空尾结点上,停止条件是while (first)。其实就是多走一步和少走一步的问题,具体处理跟每个人的习惯和喜好有关。
笔者在这有一个建议,建议大家把首指针停在链表最后一个非空子节点上。原因如下:
1:首指针停在空节点上没有具体的意义,而停在链尾非空节点上,好歹知道这是链尾的有效节点。该题目就有效利用了这一信息,不然还得重新遍历找到右边链表的末节点。
2:若停在空节点上,计算首指针先走几步的时候,可能往往和实际的参数有差(例如该题的k),而这个数值,可能让你不知道它是k+1,还是k-1,或是len-k,很容易糊涂。而停在尾部非空子节点上时,分析起来相对比较明朗,不容易出错。
这里也不是出于投机,是一个潜意识中的良好习惯,更不容易出错的一种行为方式。
解法:双指针
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
int cntListNodes(ListNode *head) {
if (NULL == head)
return 0;
int len = 0;
while (head) {
++len;
head = head->next;
}
return len;
}
public:
ListNode* rotateRight(ListNode* head, int k) {
if (NULL == head || NULL == head->next || k < 1)
return head;
int len = cntListNodes(head); //计算长度,求实际移动位置
k %= len;
if (k == 0)
return head;
ListNode *first = head, *second = head; //双指针
while (k--)
first = first->next;
while (first && first->next) { //first要停在链表最后一个非空节点上
first = first->next;
second = second->next;
}
first->next = head; //将链表收尾相连,避免使用临时变量
head = second->next; //head指向新头节点
second->next = NULL; //尾节点置空,防止链表成环
return head;
}
};