题目介绍
这是LeetCode上面第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
解题思路
原题意思就是说,会给定一个k值,如果k = 1,那么整个链表向右移动一位,如果k = 2,那么整个链表向右移动两位,也就是说整个链表会向右移动k位。
这个题目比较头疼的是,结点右移到NULL时,又重新回到首节点开始右移,所以对于如何确定移动后的结点位置,是这道题目的关键。
我的想法是既然结点右移到NULL处,又重新回到首节点,那么,我们可以直接将这个单链表组成一个环形链表。即:tail->next = head,如图所示:
那么在找到移动后的尾结点的位置后,就可以将尾节点的next指向NULL。重新将这个链表恢复成单链表。如图所示:
那么现在的问题变成了如何找到右移后的尾节点?
请先看下面的一张图,我画出了当k = 1,k = 2, k = 3 时的结点情况。
先需要找到原链表的尾节点,即图中的original tail,通过尾节点的移动规律找到移动后的尾节点,即图中的tail,下面我们来具体分析一下。
当k = 1时,tail的值为3, original tail需要通过 4->1->2->3 才能查找到3,进行查找的次数为3次。
当k = 2时,tail的值为2,original tail需要通过 4->1->2 才能查找到2,进行查找的次数为2次。
当k = 3时,tail的值为1,original tail需要通过 4->1 才能查找到1,进行查找的次数为1次。
可以通过上述的分析得到,假定链表的总结点为n,移动次数为k,查找次数为m,那么有:n = m + k,这样我们就可以得到通过original tail查找得到移动后的tail所需要查找的次数 m = n - k;
还存在一种情况:当移动的次数k大于或等于总结点数n时,这个规律又是怎样呢?请看下图:
当k = 4时,tail的值为4,与original tail 相同,无需查找 original tail 即为 tail。
当k = 5时,tail的值为3, original tail需要通过 4->1->2->3 才能查找到3,进行查找的次数为3次。而可以看出,当k = 5时,实际上与k = 1 的情况相同。
当k = 6时,tail的值为2, original tail需要通过 4->1->2 才能查找到2,进行查找的次数为2次。而可以看出,当k = 5时,实际上与k = 2 的情况相同。
通过分析,可以看到,实际上为4次一循环就会与原链表相同,4是结点的总个数n,所以我们只需要通过 对k除以n 取余 即:k % n, 然后可以得出需要查找的次数m = n - (k % n),如果这个规律通过我的解释还是没弄明白的话,可以自己在纸上画一下移动的情况 ,自己领悟会更深刻一点。
这里其实还有一个小的注意点,我们讨论的情况k的取值只包括k >= 0,当k < 0,时程序该怎么处理?
实际上,题目中已经给出了k是非负数,所以在代码中可以不做处理。但在实际的编写中,当k < 0时,要做一下处理,是抛出异常,还是返回原链表,还是向左移动k位,这些要根据实际情况来决定。
好了,总体的思路就是以上,下面是具体实现。
最后想说一下,实现这道题目的方法有很多种,或许有更优的解法,我还不知道,我也仅仅是提供我在做这道题时的一个思路,供大家参考。
代码实现(使用c++语言)
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
// 当链表为NULL或者只存在一个节点,直接返回head.
if (!head ||! head->next) {
return head;
}
// 循环一次,记录节点总数以及找到链表的最后一个节点
ListNode *curNode = head;
int totalNode = 1;
while (curNode->next) {
totalNode ++;
curNode = curNode->next;
};
// 记录一下这是尾节点
ListNode *tail = curNode;
// 首尾相连 组成一个环形链表
tail->next = head;
// 计算循环次数 - 如果不太理解这个步骤,可以自己【画图理解]。
int count = totalNode - (k % totalNode);
// 找到右移后的链表中最后一个节点
for (int i = 0; i < count; i++) {
tail = tail->next;
};
// 确认右移后链表的头结点
ListNode *retNode = tail->next;
// 将组成的环形链表的尾节点断开,恢复成单链表。
tail->next = NULL;
return retNode;
}
};
github地址
最后给上这道题的github地址:传送门。