一、题目描述
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
示例 1:
输入:head = [1,2,3,4,5], k = 2 输出:[4,5,1,2,3]
示例 2:
输入:head = [0,1,2], k = 4 输出:[2,0,1]
提示:
- 链表中节点的数目在范围
[0, 500]
内 -100 <= Node.val <= 100
0 <= k <= 2 * 10^9
二、解题思路
-
确定链表的长度:首先,我们需要确定链表的长度。这可以通过遍历链表并计数节点来完成。得到链表长度后,我们可以对 k 进行取模操作,因为如果 k 大于链表的长度,旋转的效果与旋转 k 模链表长度是相同的。
-
连接链表尾部到头部:找到链表的尾节点(即最后一个节点),并将尾节点的 next 指向头节点,从而形成一个环形链表。
-
找到新的尾节点:我们需要找到新的尾节点,即原链表的第 (length - k) 个节点。我们可以通过从头部开始的指针遍历链表来找到这个节点。
-
更新头节点和尾节点:将新找到的尾节点的 next 置为 null,这样就将环形链表断开,形成原始的非环形链表。此时,新尾节点的前一个节点就是新的头节点。
-
返回新的头节点。
三、具体代码
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if (head == null || head.next == null) {
return head;
}
// Step 1: Find the length of the linked list
ListNode current = head;
int length = 1;
while (current.next != null) {
current = current.next;
length++;
}
// Step 2: Connect the end of the list to the head (make a circular list)
current.next = head;
// Step 3: Find the new tail node (the (length - k)th node from the beginning)
// and also find the new head node
current = head;
k = k % length; // In case k is larger than the list length
for (int i = 0; i < length - k - 1; i++) {
current = current.next;
}
// Step 4: Update the new head and tail nodes
ListNode newHead = current.next;
current.next = null;
// Step 5: Return the new head of the list
return newHead;
}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 寻找链表长度:这部分代码需要遍历整个链表,因此时间复杂度是 O(n),其中 n 是链表的节点数。
- 连接链表形成环形:这一步仅仅是将链表的尾节点指向头节点,时间复杂度是 O(1)。
- 找到新的头节点和尾节点:我们需要找到第 (length - k) 个节点,这需要遍历链表,时间复杂度是 O(n)。
- 更新头尾节点:这一步也是常数时间操作,时间复杂度是 O(1)。
- 综上所述,主要的时间消耗在寻找链表长度和找到新的头节点这两个步骤,因此总的时间复杂度是 O(n)。
2. 空间复杂度
- 代码中使用了常数个额外空间,即一些指针变量(如
current
和newHead
),以及一个整数变量length
和k
。 - 由于没有使用任何与输入链表大小成比例的数据结构,所以空间复杂度是 O(1)。
五、总结知识点
-
链表数据结构:链表是一种线性数据结构,其中的每个节点包含数据和指向下一个节点的指针。在这个问题中,使用了单链表,每个
ListNode
包含val
(存储数据)和next
(指向下一个节点的指针)。 -
遍历链表:代码中的
while
循环用于遍历链表以确定链表的长度。这是处理链表时常见的操作,需要一直遍历直到链表的尾部。 -
链表的尾递归:为了创建一个环形链表,代码中将链表的尾节点的
next
指针指向了头节点。这是实现链表旋转的关键步骤。 -
取模运算:在进行链表旋转时,
k
可能大于链表的长度。使用k = k % length;
可以确保旋转的有效次数不会超过链表的长度,这是一种常见的优化手段。 -
指针操作:代码中通过指针操作来找到新的头节点和尾节点。在链表中,指针操作是非常关键的技能,因为它允许我们在不使用额外空间的情况下修改链表的结构。
-
断开链表:为了完成链表的旋转,需要将环形链表断开,这通过将尾节点的
next
指针设置为null
来实现。 -
边界条件:代码在开始时检查了链表是否为空或只有一个节点,这是处理链表问题时常见的边界条件检查,确保了算法的健壮性。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。