61. Rotate List**
https://leetcode.com/problems/rotate-list/
题目描述
Given a linked list, rotate the list to the right by k places, where k is non-negative.
Example 1:
Input: 1->2->3->4->5->NULL, k = 2
Output: 4->5->1->2->3->NULL
Explanation:
rotate 1 steps to the right: 5->1->2->3->4->NULL
rotate 2 steps to the right: 4->5->1->2->3->NULL
Example 2:
Input: 0->1->2->NULL, k = 4
Output: 2->0->1->NULL
Explanation:
rotate 1 steps to the right: 2->0->1->NULL
rotate 2 steps to the right: 1->2->0->NULL
rotate 3 steps to the right: 0->1->2->NULL
rotate 4 steps to the right: 2->0->1->NULL
C++ 实现 1
先介绍我最新的思路吧, 然后再看看以前的做法.
下面代码的思路是:
- 先翻转整个链表
- 翻转前
k
个节点, 再翻转后num - k
个节点 - 拼接两个子链表
大致思路如上, 但是有一些细节需要考虑, 下面依次介绍:
- 首先,
k
有可能非常大, 如果我们知道了链表的节点个数num
, 那么只需要处理k %= num
的情况. 为了统计链表的节点个数, 这一步在翻转这个链表时完成, 所以在reverse
函数中增加了int &num
这个参数. - 我们只需要考虑
k %= num
的情况, 如果此时k == 0
, 那么直接返回原始链表即可, 但由于在第一步中已经将链表翻转了, 所以要注意翻转回去. - 之后进入到第 2 个步骤, 原本是打算先翻转前
k
个节点, 再翻转后面的num - k
个节点, 但是会发现这有个困难就是无法有效的拼接两个链表, 如果要拼接两个链表, 需要先找到前一个子链表的最后一个节点, 当然这样做也是可以的, 但感觉不太优雅. - 因此, 换种方式是, 先翻转后面的
num - k
个节点, 当翻转完之后, 效果如下:
- 之后第三步, 需要先翻转
5->4
, 并让5
指向1
. 这个思路其实和reverse
函数中的逻辑是一致的.
但有个细节需要注意,head
最多移动到4
, 相当于3
是 head 的终点, 需要用end = p->next
先将3
保存到end
中, 而不能在while
循环中直接使用head != p->next
; 这是因为节点4
最后会发生变化, 相应的 p 也会发生变化, 导致最后p->next
不再指向 3. 下面用图简单描述一下第三步:
class Solution {
private:
ListNode* reverse(ListNode *head, int &num) {
ListNode *prev = nullptr;
while (head) {
auto tmp = head->next;
head->next = prev;
prev = head;
head = tmp;
num += 1;
}
return prev;
}
public:
ListNode* rotateRight(ListNode* head, int k) {
if (!head || !head->next) return head;
int num = 0;
// 第一步
head = reverse(head, num);
k = k % num;
if (k == 0) return reverse(head, num);
auto p = head;
while (--k) p = p->next;
// 第二步
auto rlist = p->next;
rlist = reverse(rlist, num);
// 第三步
auto end = p->next;
while (head != end) {
auto tmp = head->next;
head->next = rlist;
rlist = head;
head = tmp;
}
return rlist;
}
};
C++ 实现 2
两年前的代码:
思路: 使用递归可以非常简单的解决这道题目, 如果要将链表旋转 k 次, 那么可以在旋转 k - 1 次的基础上, 将链表的最后一个节点放在链表的头部, 这样就得到了第 k 次的链表. 但是有个优化必须注意, 否则会报超时, 毕竟 k 可能很大, 我们观察到, 如果 k 能整除链表中节点的个数, 那么旋转后的链表将是原链表本身.
class Solution {
private:
ListNode* rotateRight(ListNode *head, int n, int k) {
// 如果 k 能整除 n, 那么直接返回链表本身
if ((k % n) == 0) return head;
// 否则只要在第 k-1 次旋转得到的链表的基础上, 做一些简单的操作即可.
// 注意 while 循环中判断 ptr->next->next 是否存在, 这样是为了让
// ptr 最终的值是倒数第二个节点.
head = rotateRight(head, n, k - 1);
auto ptr = head;
while (ptr->next && ptr->next->next)
ptr = ptr->next;
auto tmp = ptr->next;
ptr->next = nullptr;
tmp->next = head;
head = tmp;
return head;
}
public:
ListNode* rotateRight(ListNode* head, int k) {
if (!head || !head->next) return head;
// 用 n 统计链表中节点的个数
int n = 0;
auto ptr = head;
while (ptr) {
ptr = ptr->next;
++ n;
}
return rotateRight(head, n, k);
}
};
C++ 实现 3
来自 LeetCode 讨论区:
My clean C++ code, quite standard (find tail and reconnect the list)
这种做法的思路是, 首先求出链表的大小, 并找到最后一个节点 tail, 然后将链表弄成环状 tail->next = head
. 之后根据 k %= len
, 判断何处断开链表. (如果 if 语句中没有使用 k %= len
, 那么 for 循环中的判断条件 i < len - k
实际上是 i < len - k % len
.
相当于先成环, 再断开. 最后 tail
要移动 len - k
步, 直观一点去想, 如果指针能向后移动, 那么向后移动 k
步最方便; 如果不能向后移动, 那么就能向前移动 len - k
步.
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(!head) return head;
int len=1; // number of nodes
ListNode *newH, *tail;
newH=tail=head;
while(tail->next) // get the number of nodes in the list
{
tail = tail->next;
len++;
}
tail->next = head; // circle the link
if(k %= len)
{
for(auto i=0; i<len-k; i++) tail = tail->next; // the tail node is the (len-k)-th node (1st node is head)
}
newH = tail->next;
tail->next = NULL;
return newH;
}
};