0
点赞
收藏
分享

微信扫一扫

61. Rotate List**

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

先介绍我最新的思路吧, 然后再看看以前的做法.

下面代码的思路是:

  1. 先翻转整个链表
  2. 翻转前​​k​​​ 个节点, 再翻转后​​num - k​​ 个节点
  3. 拼接两个子链表

61. Rotate List**_链表

大致思路如上, 但是有一些细节需要考虑, 下面依次介绍:

  1. 首先,​​k​​​ 有可能非常大, 如果我们知道了链表的节点个数​​num​​​, 那么只需要处理​​k %= num​​​ 的情况. 为了统计链表的节点个数, 这一步在翻转这个链表时完成, 所以在​​reverse​​​ 函数中增加了​​int &num​​ 这个参数.
  2. 我们只需要考虑​​k %= num​​​ 的情况, 如果此时​​k == 0​​, 那么直接返回原始链表即可, 但由于在第一步中已经将链表翻转了, 所以要注意翻转回去.
  3. 之后进入到第 2 个步骤, 原本是打算先翻转前​​k​​​ 个节点, 再翻转后面的​​num - k​​ 个节点, 但是会发现这有个困难就是无法有效的拼接两个链表, 如果要拼接两个链表, 需要先找到前一个子链表的最后一个节点, 当然这样做也是可以的, 但感觉不太优雅.
  4. 因此, 换种方式是, 先翻转后面的​​num - k​​ 个节点, 当翻转完之后, 效果如下:

61. Rotate List**_链表_02

  1. 之后第三步, 需要先翻转​​5->4​​​, 并让​​5​​​ 指向​​1​​​. 这个思路其实和​​reverse​​​ 函数中的逻辑是一致的.
    但有个细节需要注意,​​​head​​​ 最多移动到​​4​​​, 相当于​​3​​​ 是 head 的终点, 需要用​​end = p->next​​​ 先将​​3​​​ 保存到​​end​​​ 中, 而不能在​​while​​​ 循环中直接使用​​head != p->next​​​; 这是因为节点​​4​​​ 最后会发生变化, 相应的 p 也会发生变化, 导致最后​​p->next​​ 不再指向 3. 下面用图简单描述一下第三步:

61. Rotate List**_链表_03

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;
}
};

举报

相关推荐

0 条评论