61. 旋转链表
题目来源
题目分析
题目描述
给你一个链表的头节点 head,旋转链表,将链表每个节点向右移动 k 个位置。
题目难度
- 难度:简单
题目标签
- 标签:链表
题目限制
- 链表中节点的数目在范围
[0, 500]内 -100 <= Node.val <= 1000 <= k <= 2 * 10^9
解题思路
思路:模拟旋转过程
- 问题定义:
- 需要将链表的节点向右移动
k个位置。
- 需要将链表的节点向右移动
- 核心算法:
- 首先计算链表的长度
len。 - 计算实际需要移动的步数
k % len,以避免不必要的完整循环。 - 使用双指针找到新的头节点和尾节点。
- 首先计算链表的长度
- 关键步骤:
- 计算链表长度
len。 - 计算实际需要移动的步数
k % len。 - 使用两个指针
pre和cur,先将cur移动k % len步。 - 然后同时移动
pre和cur,直到cur到达链表的最后一个节点。 - 将
cur.next指向原来的头节点head,形成一个环。 - 更新新的头节点为
pre.next。 - 将
pre.next置为null,断开环。
- 计算链表长度
- 返回结果:
- 返回新的头节点。
核心算法步骤
- 计算链表长度:
- 初始化
len为 1。 - 使用
tail指针遍历链表,计算链表的总长度len。
- 初始化
- 计算实际移动步数:
- 计算
k % len,得到实际需要移动的步数。
- 计算
- 使用双指针找到新的头节点和尾节点:
- 初始化
pre和cur指针都指向头节点head。 - 先将
cur指针移动k % len步。 - 然后同时移动
pre和cur指针,直到cur到达链表的最后一个节点。
- 初始化
- 更新链表:
- 将
cur.next指向原来的头节点head,形成一个环。 - 更新新的头节点为
pre.next。 - 将
pre.next置为null,断开环。
- 将
- 返回结果:
- 返回新的头节点。
代码实现
以下是 Java 代码实现:
/**
* 61. 旋转链表
* @param head 链表头结点
* @param k 移动位置
* @return ListNode 旋转后的链表头结点
*/
public ListNode rotateRight(ListNode head, int k) {
if (head == null || head.next == null || k == 0) return head;
int len = 1;
ListNode pre = head;
ListNode cur = head;
ListNode tail = head;
while (tail.next != null) {
tail = tail.next;
len++;
}
k = k % len;
for (int i = 0; i < k; i++) {
cur = cur.next;
}
while (cur.next != null) {
pre = pre.next;
cur = cur.next;
}
cur.next = head;
head = pre.next;
pre.next = null;
return head;
}
代码解读
- 计算链表长度:通过遍历链表,计算链表的总长度
len。 - 计算实际移动步数:计算
k % len,得到实际需要移动的步数。 - 使用双指针找到新的头节点和尾节点:
- 先将
cur指针移动k % len步。 - 然后同时移动
pre和cur指针,直到cur到达链表的最后一个节点。
- 先将
- 更新链表:
- 将
cur.next指向原来的头节点head,形成一个环。 - 更新新的头节点为
pre.next。 - 将
pre.next置为null,断开环。
- 将
- 返回结果:返回新的头节点。
性能分析
- 时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表两次,一次计算长度,一次找到新的头节点。
- 空间复杂度:O(1),除了用于存储结果的链表外,使用的额外空间是常数级别的。
测试用例
你可以使用以下测试用例来验证代码的正确性:
// 测试用例1
ListNode head1 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5)))));
int k1 = 2;
System.out.println(rotateRight(head1, k1)); // 输出: [4, 5, 1, 2, 3]
// 测试用例2
ListNode head2 = new ListNode(0, new ListNode(1, new ListNode(2)));
int k2 = 4;
System.out.println(rotateRight(head2, k2)); // 输出: [2, 0, 1]
// 测试用例3
ListNode head3 = new ListNode(1, new ListNode(2));
int k3 = 1;
System.out.println(rotateRight(head3, k3)); // 输出: [2, 1]
// 测试用例4
ListNode head4 = new ListNode(1);
int k4 = 0;
System.out.println(rotateRight(head4, k4)); // 输出: [1]
// 测试用例5
ListNode head5 = null;
int k5 = 3;
System.out.println(rotateRight(head5, k5)); // 输出: null
扩展讨论
其他实现
- 递归方法:虽然递归可以实现链表的旋转,但可能会增加代码的复杂度,并且对于这个问题,迭代方法已经足够清晰和高效。
- 方向数组:对于链表旋转问题,方向数组并不是必需的,因为旋转操作主要涉及指针的重新连接。
性能优化
- 空间优化:在上述实现中,我们已经做到了空间复杂度为 O(1),除了输出链表外没有使用额外的空间。
- 边界检查:在计算实际移动步数时,通过
k % len避免了不必要的完整循环,这是性能优化的一部分。
总结
这道题目考察了对链表操作的理解和实现能力。通过模拟旋转的过程,我们可以有效地解决这个问题。在实际应用中,类似的链表操作可能会用于数据结构的转换、图像处理等领域。掌握这种操作对于解决更复杂的问题具有重要的基础意义。
希望这份文档能够帮助你更好地理解和实现旋转链表的问题!如果有任何进一步的问题或需要更多的优化,请随时告知。

1100

被折叠的 条评论
为什么被折叠?



