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\)

解法1

一开始可以发现,有如下三种特殊情况是可以直接不进行任何处理,直接返回head的:

  • \(head\)为\(null\),整个链表都是\(null\)
  • \(head\)的\(next\)为空,代表链表只有一个元素,此时不论右移多少次,都是本身
  • \(k=0\)

考虑\(k\)的取值, 如果\(k\)的值正好是链表长度的整数倍,这种情况右移后还是原链表本身。返回即可。
接下来,如果\(k\)远远超过链表的长度,我们也没必要进行过多的右移,获取\(k\)对链表长度的余数即为右移的次数。
一个难点在于,在单向链表中找到一个元素的父亲节点的时间复杂度很高。此时第一个想法是翻转链表、新链表左移、再次翻转链表。
也写出了第一版代码。

代码1

java实现

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        //三种情况,链表为空、链表只有一个元素、移动位置为0
        if (head == null ||head.next == null || k == 0) {
            return head;
        }
        ListNode tail = head;
        ListNode temp = head;
        //判断长度
        int i = 1;
        while (temp.next != null) {
            temp = temp.next;
            i++;
        }
        
        k = k % i;
        //当旋转整数倍时,没有必要挪动数据,直接返回head
        if (k == 0) {
            return head;
        }
        
        //翻转链表
        head = reverse(head);
        while(k>0) {
            tail.next = head;
            tail = head;
            head = head.next;
            tail.next = null;
            k--;
        }
        head = reverse(head);
        return head;
        
    }
    
    //翻转链表
    ListNode reverse (ListNode head) {
        ListNode prev = head;
        ListNode curr = head.next;
        while (curr!=null) {
            ListNode temp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = temp;
        }
        head.next = null;
        return prev;
    }
}

解法2

解法1的问题在于有两次翻转链表,虽然只是\(O(n)\)的时间复杂度,但是本着能少一步是一步的原则,还是能省则省。
继续观察示例1,发现,右移\(2\)位,实际上是左移\(3\)位,而左移是解法1里就实现了的。这就不需要翻转链表就可以直接移动了。

代码2

java实现

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        //三种情况,链表为空、链表只有一个元素、移动位置为0
        if (head == null ||head.next == null || k == 0) {
            return head;
        }
        ListNode tail = head;
        ListNode temp = head;
        //判断长度
        int i = 1;
        while (tail.next != null) {
            temp = tail;
            tail = tail.next;
            i++;
        }
        
        k = k % i;
        //当旋转整数倍时,没有必要挪动数据,直接返回head
        if (k == 0) {
            return head;
        }
        //增加了一步k的计算
        k = i-k;
        
        //翻转链表
        //注释掉解法1的翻转链表
        //head = reverse(head);
        while(k>0) {
            tail.next = head;
            tail = head;
            head = head.next;
            tail.next = null;
            k--;
        }
        //注释掉解法1的翻转链表
        //head = reverse(head);
        return head;
        
    }
    
    //翻转链表
    ListNode reverse (ListNode head) {
        ListNode prev = head;
        ListNode curr = head.next;
        while (curr!=null) {
            ListNode temp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = temp;
        }
        head.next = null;
        return prev;
    }
}

仔细观察代码,实际上是去掉了翻转链表,对k进行了更进一步的操作而已。相比解法一,代码更加的清晰。