61. 旋转链表
✨核心逻辑
本题采用 闭环后断链(先成环,再切断) 的策略:
- 边界处理:如果链表为空,或者只有一个节点,直接返回原链表即可。
- 计算链表长度并闭环:先遍历整个链表,拿到链表的节点总数
length,同时让尾节点的next指向头节点head,形成一个闭环。 - 取模优化:如果右移的步数
k恰好等于链表长度的整数倍,由于转回原样,直接返回原链表即可。 - 寻找新的尾节点:要找到旋转后新的头节点,我们需要先找到新链表的尾节点。经过数学推导,这个新的尾节点正好位于原链表的
length - (k % length)的位置。 - 断开环:找到新的尾节点后,记录它的下一个节点作为待返回的新头节点,然后将新尾节点的
next置为null,断开环,返回新头节点。
🔥代码实现(含详细变量注释)
class Solution {
public ListNode rotateRight(ListNode head, int k) {
// 边界情况判断:如果链表为空,或者只有一个节点,旋转后依旧是原链表,直接返回
if (head == null || head.next == null) {
return head;
}
// copy:辅助遍历指针,初始指向头节点,用于计算链表长度并最终定位到原链表的尾节点
ListNode copy = head;
// length:记录链表的总节点数,因为至少有一个节点,所以初始化为 1
int length = 1;
// 获取链表长度,并将 copy 指针移动到原链表最后一个节点(尾节点)
while (copy.next != null) {
copy = copy.next;
length++;
}
// 如果向右移动的步数 k 刚好是链表长度的倍数,等于没有移动,直接返回原头节点
if (k % length == 0) {
return head;
}
// sum:计算旋转后,成为新链表尾节点在原链表中的顺位位置(从 1 开始计数)。
// 例如 length=5, k=2,则新的尾节点是原链表的第 3 个节点。
int sum = length - k % length;
// 将原链表的尾节点 copy 的 next 指向 head,使链表首尾相连形成闭环
copy.next = head;
// 从原链表的头节点开始,向后移动 sum - 1 次,找到新的尾节点(此时 head 变为了新尾节点)
for (int i = 0; i < sum - 1; i++) {
head = head.next;
}
// newCopy:记录新尾节点的下一个节点,这个节点也就是旋转后新链表的真实头节点
ListNode newCopy = head.next;
// 将找到的新尾节点的 next 置为 null,断开整个闭环,完成链表旋转
head.next = null;
// 返回旋转后新链表的新头节点
return newCopy;
}
}
- ⏱️复杂度分析
时间复杂度:O(N),其中 N 是链表的长度。我们要先遍历一次链表获取长度和连接成环,然后再遍历一次定位新的尾节点并断开,总的时间复杂度为线性 O(N)。
空间复杂度:O(1),仅使用了 copy、length、sum、newCopy 等几个额外的指针和整型变量,没有使用额外的数组空间。
🔍总结一下:
这道题的关键在于成环,就是尾节点指向头节点,进而再找到新链表的头节点,断开定位的尾节点即可