之前写的现在看起来很啰嗦,更新为python的
甚至都不需要用临时nxt记录断开前的next指向,我们顺序写的好,一次就能把pre first 和second的指向搞定
pre指向 second ,first 指向second的下一个 (这一步最关键,second.next现在已经被重新指向了,第三步我们改second.next的方向就没有任何问题了) ,secode 指向first节点。 然后更新pre和cur节点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(-1,head)
pre_node = dummy
cur_node =head
while cur_node and cur_node.next :
# 获取需要交换的两个节点
first_node=cur_node
second_node =cur_node.next
# --- 开始交换 ---
# 1. 将前一个节点的 next 指向第二个节点 (修正)
pre_node.next =first_node.next #pre_node.next =second_node
# 2. 将第一个节点的 next 指向第二个节点的下一个节点
first_node.next=second_node.next
# 3. 将第二个节点的 next 指向第一个节点,完成交换
second_node.next=first_node
# --- 更新指针,为下一次循环做准备 ---
# 1. 更新 pre_node 为交换后的第一个节点 (现在是 pair 的末尾)
pre_node =first_node
# 2. 更新 cur_node 为下一对需要交换的第一个节点
cur_node=first_node.next
return dummy.next
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 创建哑节点,简化边界处理
# Create a dummy node to simplify edge cases.
dummy = ListNode(0, head)
# pre_node 指向要交换的节点对之前的那个节点
# pre_node points to the node before the pair to be swapped.
pre_node = dummy
# first_node 指向当前要处理的节点对的第一个节点,作为循环的主要控制变量
# first_node points to the first node of the current pair to process, acting as the main loop control variable.
first_node = head
# 循环条件:只要 first_node 和它后面的节点 (构成一对) 都存在
# Loop condition: As long as first_node and the node after it (forming a pair) both exist.
while first_node and first_node.next:
# second_node 是当前要处理的节点对的第二个节点
# second_node is the second node of the current pair to process.
second_node = first_node.next
# --- 开始交换 ---
# 1. pre_node 指向 second_node
# Link pre_node to second_node.
pre_node.next = second_node
# 2. first_node 指向 second_node 原来的下一个节点
# Link first_node to the original node after second_node.
first_node.next = second_node.next
# 3. second_node 指向 first_node
# Link second_node back to first_node.
second_node.next = first_node
# --- 交换结束 ---
# --- 更新指针,为下一次循环做准备 ---
# 1. 更新 pre_node 为交换后的第一个节点 (即原来的 first_node)
# Update pre_node to the node that was originally first_node (now the end of the swapped pair).
pre_node = first_node
# 2. 更新 first_node 为下一对需要交换的第一个节点
# Update first_node to the start of the next pair.
first_node = first_node.next # 注意:此时 first_node.next 已经是下一对的起点了
# 返回哑节点的下一个节点,即新链表的头
# Return the node after the dummy node, which is the new head of the list.
return dummy.next
24. 两两交换链表中的节点
草图:
这道题目需要注意的是2个节点进行交换,分为奇偶数情况。
- 要操作2个节点的互换,得找到第一个节点的前一个节点作为cur节点。 这样指针才能成功指向第一个节点的后面2个节点。 目的是更换之前,能成功用临时变量把相关节点都保存下来,这样在互换后还能找到节点。
- 如上图所示, 节点1和节点2要更换,就得在节点1之前设置一个cur节点,此时是一个虚拟节点dummy。 dummy节点指向2节点,2节点指向1节点,1节点指向3节点。即:cur->2->1->3->4->null ;进行3、4的互换时候, 需要把cur节点移动到节点3 前面,也就是cur=节点1。
- 我常犯的错误是:直接让 cur.next =cur.next.next; 这样就把原来dummy-->节点1的指引链断了,节点1无法找到。 所以一定要在重新指引前,做好节点1的储存:temp=cur.next; 同理,节点2指向节点1后,节点2->3的链条断裂。 我们需要做好节点3的储存temp1=cur.next.next;
- 遍历循环时候,这2个顺序不能写反,否则会出现空指针异常。
实际代码如下:
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead;
while (cur.next != null && cur.next.next != null) {
ListNode temp = cur.next; // 保存第一个节点
ListNode temp1 = cur.next.next.next; // 保存第二个节点后面的节点
cur.next = cur.next.next; // 将当前指针的下一个指向第二个节点,dummy -> 节点2
cur.next.next = temp; // 将第二个节点的下一个指向第一个节点,节点2 -> 节点1
temp.next = temp1; // 将第一个节点的下一个指向原本第三个节点(即交换后的下一个部分),节点1 -> 节点3
// 更新 cur 指针,移动到下一个需要处理的节点
cur = cur.next.next;
}
return dummyHead.next;
}
}
或者用更加见名知义的方式来写代码:
public ListNode swapPairs(ListNode head) {
ListNode dummyHead= new ListNode(-1);
dummyHead.next =head;
ListNode cur=dummyHead;
while(cur.next !=null && cur.next.next !=null ){
ListNode firstNode =cur.next;
ListNode secondeNode =cur.next.next;
ListNode temp=cur.next.next.next;
cur.next= secondeNode;//dummy-->节点2
secondeNode.next=firstNode;//节点2-->节点1
firstNode.next=temp;//节点1--->节点3
//更新cur
cur=cur.next.next;
}
return dummyHead.next;
}
或者使用这样:
public ListNode swapPairs(ListNode head) {
ListNode dummyHead= new ListNode(0,head);
ListNode cur=dummyHead;
while(cur.next !=null && cur.next.next !=null ){
ListNode firstNode =cur.next;
ListNode secondeNode =cur.next.next;
ListNode temp =secondeNode.next;
secondeNode.next=firstNode;
firstNode.next=temp;
cur.next=secondeNode;
cur =firstNode;
}
return dummyHead.next;
}
或者使用这种: 我们选择从头到尾,一步步断开之前的链接关系,每断开一个就重新给予新的链接关系 ,这样的逻辑上会比较清晰好梳理。
public ListNode swapPairs(ListNode head) {
ListNode dummyHead= new ListNode(0,head);
ListNode cur=dummyHead;
while(cur.next !=null && cur.next.next !=null ){
ListNode firstNode =cur.next;
ListNode secondeNode =cur.next.next;
//调整顺序之后,当前节点指向第二个节点
cur.next= secondeNode;//dummy-->节点2
//第一个节点指向第三个节点
firstNode.next=secondeNode.next;
//第二个节点指向第一个节点
secondeNode.next=firstNode;//节点2-->节点1
//更新cur
cur=firstNode;
}
return dummyHead.next;
}
或者我们使用递归的思路:
public ListNode swapPairs(ListNode head){
if(head ==null || head.next ==null){
return head;
}
//find next node
ListNode next =head.next;
//use functinon swapPairs
ListNode newNode =swapPairs(next.next );
//change each other
next.next =head;
head.next =newNode ;
return next;
}
public ListNode swapPairs(ListNode head) {
// base case 退出条件
if (head == null || head.next == null) {
return head;
}
// 获取当前节点的下一个节点
ListNode next = head.next;
// 进行递归,处理 next 后面的链表
ListNode newNode = swapPairs(next.next);
// 交换当前两个节点
next.next = head;
head.next = newNode;
// 返回新的头节点
return next;
}
递归的压栈和弹栈过程
递归函数的执行过程可以想象为栈的操作,每次递归调用都将一个新函数调用压入栈中,而在递归完成后,函数会从栈中弹出并返回相应的值。
对于你的问题,我们可以一步一步来看递归过程中发生了什么,假设我们有一个链表 1 -> 2 -> 3 -> 4
,递归调用过程如下:
第一次调用 swapPairs(1)
- 参数
head = 1
,并且head.next = 2
存在。 - 进入函数内部,
next = head.next
,即next = 2
。 - 准备进行递归调用
swapPairs(next.next)
,即swapPairs(3)
。 - 在此时,当前函数调用被压入栈中,并且控制权转移到递归调用
swapPairs(3)
。
第二次调用 swapPairs(3)
- 参数
head = 3
,并且head.next = 4
存在。 - 进入函数内部,
next = head.next
,即next = 4
。 - 准备进行递归调用
swapPairs(next.next)
,即swapPairs(null)
。 - 在此时,当前函数调用被压入栈中,并且控制权转移到递归调用
swapPairs(null)
。
第三次调用 swapPairs(null)
- 参数
head = null
,由于head == null
,达到了递归的退出条件。 - 函数直接返回
null
。 - 这一返回操作相当于将栈顶的调用(第三次调用)从栈中弹出,并将返回值传递给它的上一层调用(也就是第二次调用)。
弹栈过程:回到第二次调用 swapPairs(3)
- 现在递归结束,控制权从第三次调用返回到了第二次调用。
- 在第二次调用中,原来保存的参数
head = 3
和next = 4
。 - 第三次调用返回的值为
null
,并赋值给newNode
,也就是说 在递归结束后,弹栈到第二次调用时,才知道newNode = null
。
此时第二次调用的代码继续执行:
next.next = head; // 即 4 -> 3
head.next = newNode; // 即 3 -> null
next.next = head
:将节点4
的下一个节点设为3
。head.next = newNode
:将节点3
的下一个节点设为null
(因为newNode
是null
)。- 最终返回
next
,也就是节点4
,作为当前这次调用的返回值。
弹栈过程:回到第一次调用 swapPairs(1)
- 在第二次调用返回后,控制权回到了第一次调用。
- 在第一次调用中,原来的参数
head = 1
和next = 2
。 - 第二次调用返回的值为
4
,并赋值给newNode
,也就是说 在递归结束后,弹栈到第一次调用时,才知道newNode = 4
。
然后继续执行第一次调用中的剩余代码:
next.next = head; // 即 2 -> 1
head.next = newNode; // 即 1 -> 4
next.next = head
:将节点2
的下一个节点设为1
,交换完成。head.next = newNode
:将节点1
的下一个节点设为4
,连接后面的部分。- 最终返回
next
,即节点2
,作为整个链表的新头节点。
总结
- 压栈:每次递归调用都会将当前的执行状态(函数的局部变量、指针等)压入栈中,并将控制权交给下一次递归调用。
- 递归返回值的确定:递归的返回值只有在递归调用结束并弹栈时才能被确定。在这个例子中,
newNode = null
是在递归的最深层结束时确定的,然后在逐层弹栈的过程中逐步返回给上一层调用。 - 你怎么知道递归返回
newNode = null
:- 只有在递归调用结束并开始返回时,函数才会从栈中弹出,递归的返回值才会被赋值给上一层调用的
newNode
变量。 - 因此,
newNode = null
是在第三次调用返回到第二次调用(即弹栈)时,第二次调用的上下文才获得了这个返回值。
- 只有在递归调用结束并开始返回时,函数才会从栈中弹出,递归的返回值才会被赋值给上一层调用的
所以,递归的返回值只有在递归结束后,在弹栈的过程中逐层传递给上一个递归调用。你在第二次调用中得到 newNode = null
是在第三次递归结束并返回的时候才知道的,而不是在一开始递归压栈的时候就能知道。
19.删除链表的倒数第N个节点
这道题目是需要用到双指针的逻辑去处理问题 。 我们为了让指针能正确定位到倒数n这位置,
需要设置一个n的距离,当fast 和slow距离为n,并且fast在链表的末尾,那么slow就自然在倒数第n个位置了。
基于此,我们需要调整一下,因为slow要定位在删除节点前一个位置,才能进行正确的删除处理。
所以,fast和slow距离为n+1后,同时移动2个指针,直到fast到null位置。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode =new ListNode(0,head);
ListNode fast =dummyNode;
ListNode slow =dummyNode;
for(int i=0;i<=n;i++){
if(fast ==null){
return null;
}
fast =fast.next;
}
while(fast !=null){
fast=fast.next;
slow =slow.next;
}
slow.next=slow.next.next;
return dummyNode.next;
}
}
以下的代码逻辑上有点问题,这个处理fast为的null思路不恰当。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode =new ListNode(0,head);
ListNode fast =dummyNode;
ListNode slow =dummyNode;
n++; //先给n增加1,这样2个指针距离是n+1的距离了
while(n-- && fast !=null){
fast=fast.next;
}
while(fast !=null){
fast=fast.next;
slow =slow.next;
}
return dummyNode.next;
}
}
02.07. 链表相交
这道题目是要找到交点,对于链表来讲,不是数值相等的点,而是看指针引用的内存地址是否是同一处来断定为交点。意味着交点之后,2个链表会自然共享一段链表逻辑。 所以我们一定是要对长度不一的2个链表进行对齐,并且是让他们尾部对齐。 长链表前面多出来的距离就是长链表自己独立的节点。 我们通过计算 gap = 长链表长度-短链表长度,获取这个差距。 然后对长链表进行遍历gap的次数,这样就能获取长链表舍去独立节点后的起点。
step2: 现在2个链表都是对齐的状态,我们对2者进行遍历,就会逐一去进行对比。如果找到相等的就返回对应的节点,如果找不到,知道最后一个为null,停止循环,最后返出去的是null。
对于2个链表的长度计算,我们可以通过设置头节点为cur,对cur遍历++,直到cur 为null,停止while循环。
注意:我们循环结束后要重置curA和curB的节点,回复他们为头节点。
具体的代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lenA =0;
int lenB =0;
ListNode curA= headA;
ListNode curB =headB;
while(curA !=null){
lenA++;
curA=curA.next;
}
while (curB !=null){
lenB++;
curB =curB.next;
}
// 计算完长度后,重置 curA 和 curB
curA =headA;
curB =headB;
if(lenB>lenA){
int temp =lenA;
lenA=lenB;
lenB = temp;
ListNode tempNode = curA;
curA =curB;
curB = tempNode;
}
int gap =lenA-lenB;
while(gap-->0){
if(curA ==null){
return null;
}
curA=curA.next;
}
while(curA !=null && curB !=null){
if(curA==curB){
return curA;
}
curA =curA.next;
curB =curB.next;
}
return null;
}
}
计算链表的长度我们可以写成for循环
for (curA = headA; curA != null; curA = curA.next) {
lenA++;
}
还有一个快捷的方式:2个指针同时游走,第一个链表结束后继续走第二个链表。 2个指针走的路径长度是一致的。
代码如下:
//方法二 复杂度O(m+n) 双指针
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA ==null || headB == null){
return null;
}
ListNode pA =headA;
ListNode pB =headB;
while(pA !=pB){
pA =(pA ==null)? headB:pA.next;
pB =(pB ==null)? headA:pB.next;
}
return pA;
}
142.环形链表II
代码如下:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
//定义2个指针, 然后让他们走,一个跳2节点,一个跳1节点。 然后再定义他们相遇到某个节点
//然后再定义2个index ,index1 是快指针, index2是头节点。如果index1和index2 又一次相遇的话,中止
//那么说明找到了头节点index2 ,
ListNode fast =head;
ListNode slow =head;
while(fast !=null && fast.next !=null){
fast=fast.next.next;
slow =slow.next;
if (fast ==slow){
ListNode index1 =fast;
ListNode index2 =head ;
// x=z ,一个从链表的头出发,一个从相遇的位置出发,再次相遇的点就是入口位置。
while (index1 != index2){
index1 =index1.next ;
index2 =index2.next;
}
return index1;
}
}
return null;
}
}