大厂面试手撕面试题:反转链表中前 K 个节点(亲测可用的java实现)
我们需要反转链表中前 k
个节点,且可能会有多个这样的反转段(每 k
个节点为一组)。如果链表的剩余部分不满 k
个节点,我们就不对其进行反转,直接保留原样。
解题思路:
- 链表结构:
- 我们有一个单链表,定义为
ListNode
类型。每个节点包含一个值val
和指向下一个节点的指针next
。 - 目标是反转链表中前
k
个节点,并对每一组长度为k
的节点进行反转,直到链表遍历完。
- 我们有一个单链表,定义为
- 关键步骤:
- 判断当前段是否满
k
个节点: 在反转之前,我们必须确认当前的节点数是否足够k
个。如果不足,直接跳过该段,保持原链表顺序。 - 反转当前
k
个节点: 如果当前段正好有k
个节点,我们就进行反转。 - 连接反转后的链表: 反转后的链表需要和之前的链表正确连接,以保证链表结构的完整性。
- 判断当前段是否满
- 具体步骤:
- 维护一个
dummy
虚拟节点,它的next
指针指向链表头。这样可以避免链表头部的特殊情况处理,使得头节点的反转与其他节点反转保持一致。 - 使用
prevGroupEnd
指针来标记上一个反转段的最后一个节点,它在反转完成后需要连接到当前反转段的最后一个节点。 - 对每一组长度为
k
的节点进行反转。具体做法是使用prev
和curr
两个指针,通过迭代的方式完成每组的反转。 - 完成一组反转后,更新
prevGroupEnd
和head
,继续处理下一组。
- 维护一个
- 终止条件:
- 当链表遍历完,或者剩下的节点不足
k
个时,停止反转操作。
- 当链表遍历完,或者剩下的节点不足
代码详解:
public class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 如果链表为空或 k 为 1,直接返回头节点
if (head == null || k == 1) return head;
// 创建一个虚拟节点,用于简化操作
ListNode dummy = new ListNode(0);
dummy.next = head;
// prevGroupEnd 用于指向前一组反转后链表的末尾节点
ListNode prevGroupEnd = dummy;
// 当前头节点 head
while (head != null) {
// 找到当前组的最后一个节点
ListNode groupEnd = head;
int count = 1;
// 计算当前组的节点数量
while (groupEnd != null && count < k) {
groupEnd = groupEnd.next;
count++;
}
// 如果当前组的节点数不足 K 个,不需要反转,直接返回
if (groupEnd == null) break;
// 反转当前组的节点
ListNode nextGroupStart = groupEnd.next; // 保存下一个组的起始节点
ListNode prev = nextGroupStart; // prev 初始指向下一组的起始节点
ListNode curr = head; // curr 初始指向当前组的头节点
// 反转当前组的节点
while (curr != nextGroupStart) {
ListNode temp = curr.next; // 暂存当前节点的下一个节点
curr.next = prev; // 将当前节点的 next 指向 prev(反转)
prev = curr; // prev 更新为当前节点
curr = temp; // curr 更新为下一个节点
}
// 将上一组的尾节点连接到当前反转后的组的头节点
prevGroupEnd.next = groupEnd;
// 当前组的头节点(反转后)需要指向下一组的起始节点
head.next = nextGroupStart;
// 更新 prevGroupEnd 和 head,准备处理下一组
prevGroupEnd = head;
head = nextGroupStart;
}
return dummy.next; // 返回新的链表头节点
}
// 辅助方法:打印链表
public void printList(ListNode head) {
while (head != null) {
System.out.print(head.val + " ");
head = head.next;
}
System.out.println();
}
public static void main(String[] args) {
Solution solution = new Solution();
// 创建一个链表 1 -> 2 -> 3 -> 4 -> 5
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
int k = 3;
// 输出原链表
System.out.print("Original List: ");
solution.printList(head);
// 调用反转链表前 K 个节点的方法
ListNode newHead = solution.reverseKGroup(head, k);
// 输出反转后的链表
System.out.print("List after reversing first " + k + " nodes: ");
solution.printList(newHead);
}
}
详细解析:
- 虚拟节点
dummy
:dummy
节点的作用是简化链表头部的操作,避免对链表头的特殊处理。通过将dummy.next
设置为链表的头节点,我们可以统一处理链表的反转和连接。
- 变量
prevGroupEnd
:prevGroupEnd
用于连接每次反转后组的最后一个节点。初始化时,它指向虚拟节点dummy
。- 在每次反转完成后,
prevGroupEnd
被更新为当前反转组的头节点head
。
- 变量
groupEnd
和count
:groupEnd
用于记录当前反转组的最后一个节点。如果当前组不足k
个节点,则不进行反转。count
用来计算当前组的节点数。如果count
达到k
,则进行反转操作。
- 反转操作:
- 反转时,通过三个指针
prev
(指向反转后的部分),curr
(当前处理的节点),和temp
(暂存当前节点的下一个节点)来完成每一组的节点反转。 - 完成反转后,将
prevGroupEnd
的next
指向当前反转组的尾节点groupEnd
,并将head.next
指向剩余部分的起始节点。
- 反转时,通过三个指针
- 循环和终止条件:
- 每次反转
k
个节点后,将head
更新为剩余部分的起始节点,继续进行下一组的处理。 - 当剩余节点不足
k
个时,循环终止。
- 每次反转
时间复杂度:
- O(n): 每个节点会被访问并反转一次,总时间复杂度为 O(n),其中 n 是链表的节点数。
空间复杂度:
- O(1): 只使用了常数的额外空间,除了输入的链表本身。
示例:
对于链表 1 -> 2 -> 3 -> 4 -> 5
和 k = 3
,执行反转操作后,链表将变为 3 -> 2 -> 1 -> 4 -> 5
。