61. 旋转链表
主要是用来自我记录的,方便归类和查看。
典型链表题
思路:旋转链表,其实就是将后面的结点移动到前面来,所以需要有一点点的数学技巧
思考过程:
-
先根据示例,模拟了旋转的过程,发现是将后面链表移动到前面来,即 前头 — 前尾 -> 后头 — 后尾 => 后头 — 后尾 -> 前头 -> 前尾
-
又根据示例2,考虑到旋转次数 > 链表长度,想到了在该链表后面补上n个相同的链表,后面分析没有必要,直接做一个计算,将旋转次数控制在链表长度内
-
所以想到:
- 先计算出链表长度——O(N)
- k%N,余数就是链表"后"的长度
- 于是就变成快慢指针的问题:slow和fast,fast先走,走k%N步之后,slow和fast一起走,等fast到链表尾部后,slow就在“后”的起始位置——就是链表新的头
所以,就可以写代码了:
-
初始版(用到了较多的指针3个,不好):
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode rotateRight(ListNode head, int k) { if(head == null || head.next == null) return head; ListNode cur = head; int count = 1; // 计数 while(cur.next != null){ cur = cur.next; count++; } // 运行结束之后,cur指向最后一个结点(非null) k = k % count; // 取余,去掉多次循环的 if(k == 0) return head; // 循环了一遍,直接返回 ListNode slow, fast, prev = null; slow = fast = head; while(k > 0){ fast = fast.next; k--; } while(fast != null){ prev = slow; slow = slow.next; fast = fast.next; } cur.next = head; prev.next = null; // slow的前一个结点,现在的尾结点 return slow; } }
-
优化版,参考自
前面是求“后”的第一个结点,那么反过来,也可以求出尾的最后一个结点:即
count - k % count
然后将链表首尾相连,然后从“前”的尾结点断开即可:
class Solution { public ListNode rotateRight(ListNode head, int k) { if(head == null || head.next == null) return head; // 边界情况 ListNode fast = head, slow = head; int count = 1; // 记录链表的长度 while(fast.next != null){ fast = fast.next; count++; } // 此时fast指向链表最后一个结点 k = k % count; if(k == 0) return head; // 走了一个循环,直接返回 fast.next = head; // 首尾相连 k = count - k - 1; // 少移动一格,指向的就是前的尾巴 while(k > 0){ slow = slow.next; k--; } ListNode temp = slow.next; // next就是尾的头,就是最新的头 slow.next = null; return temp; } }
也是用到了3个指针,但是思路有所不同