T24:两两交换链表中的节点
题目核心是:两个两个一组,进行交换。
交换的是节点,不是数字
我们来举例只有两个节点的情况:(1->2)
原来:
1.next=2;
2.next=null
我们要变换为:
2.next=1;
1.next=null;
所以需要三步
- 保存2
- 让2指向1
- 修改1的next
当不止有一组时怎么办?
当:
1 → 2 → 3 → 4
我们发现问题:
第一组交换后,怎么连接后面的节点?
这时就需要用到虚拟头节点dummy
交换一组节点需要几个指针?
以a → b为例:
pre → a → b → next
需要四个。
例如:
dummy → 1 → 2 → 3
这里:
pre = dummy
a = 1
b = 2
next = 3
交换操作(核心)
原来:pre → a → b → next
第一步
pre.next=b;
第二步
a.next=b.next
a后面的b已经更新到per的后面,所以我们更新a.next为b的后面一位,就是b.next
第三步
b.next=a;
然后移动指针
交换完一组后:
pre = a
因为:
a 已经在后面
继续处理下一组。
代码实现
java
ListNode dummy=new ListNode(0);
dummmy.next=head;
ListNode pre=dummy;
while(pre.next!=null&&pre.next.next!=null){
//交换
pre.next=b;
a.next=b.next;
b.next=a;
//移动到下一组
pre=a
}
return dummy.next;
T25:K个一组翻转链表
核心理解:
给你:1 → 2 → 3 → 4 → 5
k = 2
要求:
每 k 个节点为一组进行反转。
本质高手:
1 找到一组 k 个节点
2 把这 k 个节点反转
3 接回原链表
整体流程
例如
1 → 2 → 3 → 4 → 5
k = 3
流程:
第一步 找到一组
1 2 3
第二步 断开链表
1 → 2 → 3
后面
4 → 5
第三步 反转
3 → 2 → 1
第四步 接回原链表
3 → 2 → 1 → 4 → 5
第五步 继续下一组
4 5
不够3个
结束。
四、为什么需要 dummy 节点
很多链表题都会写:
ListNode dummy = new ListNode(0);
dummy.next = head;
结构变成
dummy → 1 → 2 → 3 → 4 → 5
作用:
统一处理头节点
因为反转后:
3 会成为新头节点
dummy可以帮我们管理这个变化。
五、关键指针
代码里会用到几个指针:
dummy
pre
end
start
next
作用:
| 指针 | 作用 |
|---|---|
| dummy | 虚拟头节点 |
| pre | 当前组前一个节点 |
| end | 当前组最后一个节点 |
| start | 当前组第一个节点 |
| next | 下一组开始节点 |
六、完整流程图
原链表
dummy → 1 → 2 → 3 → 4 → 5
第一步 找到 k 个节点
移动 end:
1
2
3
现在
pre → dummy
start → 1
end → 3
第二步 记录下一组
next = 4
第三步 断开链表
end.next = null
现在链表
1 → 2 → 3
第四步 反转链表
调用
reverse(start)
得到
3 → 2 → 1
第五步 接回链表
原来
dummy → 1 → 2 → 3
现在
dummy → 3 → 2 → 1
然后
1 → 4 → 5
最终
dummy → 3 → 2 → 1 → 4 → 5
第六步 移动指针
pre = start
end = pre
继续处理下一组。
代码实现
java
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 创建虚拟头节点,解决头节点变化的问题
// 例如反转后新的头节点可能不是原来的 head
ListNode dummy = new ListNode(0);
dummy.next = head;
// pre:每一组反转前的那个节点
ListNode pre = dummy;
// end:用于寻找当前这一组的第 k 个节点
ListNode end = dummy;
while(true){
// 1. 找到当前这一组的第 k 个节点
// end 向前走 k 步
for(int i = 0; i < k && end != null; i++){
end = end.next;
}
// 如果不够 k 个节点,直接结束
if(end == null){
break;
}
// 2. 记录当前这一组的开始节点
// pre -> start -> ... -> end
ListNode start = pre.next;
// 3. 记录下一组开始的位置
// end -> next
ListNode next = end.next;
// 4. 断开当前这一组链表
// 例如:
// 1 → 2 → 3 → 4 → 5
// 变成
// 1 → 2 → 3 → null 4 → 5
end.next = null;
// 5. 反转当前这一组链表
// reverse(start) 返回新的头节点
// 例如 1→2→3 变成 3→2→1
pre.next = reverse(start);
// 6. 把反转后的链表接回去
// start 此时已经变成这一组的最后一个节点
// 让它指向下一组
start.next = next;
// 7. 移动指针,为下一组做准备
// pre 移动到这一组的最后一个节点
pre = start;
// end 重新回到 pre 的位置
end = pre;
}
// 返回新的头节点
return dummy.next;
}
// 反转链表函数
private ListNode reverse(ListNode head){
// pre:反转后的链表头
ListNode pre = null;
// cur:当前正在处理的节点
ListNode cur = head;
while(cur != null){
// 保存下一个节点
ListNode next = cur.next;
// 反转指针
// 例如:2 → 1
cur.next = pre;
// pre 前进一步
pre = cur;
// cur 前进一步
cur = next;
}
// 返回反转后的头节点
return pre;
}
}