算法练习 - 旋转链表

222 阅读5分钟

题目介绍

这是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地址:传送门