学习来自《代码随想录》
建议先看我的视频讲解,视频讲解中对 反转链表需要注意的点讲的很清晰了,看完之后大家的疑惑基本都解决了。
题目链接/文章讲解/视频讲解:代码随想录
题目:力扣题目链接
206 反转链表(双指针法、递归法)
1 题目描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。不能申请额外的内存空间。
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
输入:head = [] 输出:[]
2 题解
2.1 我的思路
✔️反转链表,只要将每个节点的next指针改成指向他的前一个元素,第一个元素指向NULL就可以了。
那么如何换向呢?
❌想到是遍历链表找到其最后一个元素和倒数第二个元素,开始改写node.next->前一个元素。
错误点:单链表无法回溯上一个节点,只能从头查找,元素无法后退,而且这样做会查找很多次。
所有要设计从头开始查找并从头就开始换向的方法,即方法一:双指针法。
2.2 方法一:双指针法
思路
1、反转链表,只要将每个节点的next指针改成指向他的前一个元素,第一个元素指向NULL就可以了。
2、定义一个cur指针,指向头结点,定义一个pre指针指向cur的上一个节点,初始化时指向NULL,这样头节点翻转变成尾节点时,尾节点指向null。每次循环,调换cur指向的节点的下一个节点指向方向。循环范围是cur=head开始,到cur=null为止。
由于cur调换指针方向后,与下一个节点断开联系,所以需要在调换方向前,用temp保存下一个节点,调换方向后pre和cur向后移动位置进入下一次循环。
代码
java版本
/**
* 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 reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
ListNode temp;
while(cur != null){
temp = cur.next; //先保存cur下一个节点
cur.next = pre; //再改变cur下一个节点指向,断开原下个节点连接
pre = cur; //先向后移动pre
cur = temp; //再移动cur
}
return pre;
}
}
复杂度分析:
时间复杂度:O(n)
空间复杂度:O(1)
python 版本
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur = head
pre = None
while cur:
temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur.next = pre #反转
#更新pre、cur指针
pre = cur
cur = temp
return pre
2.3方法二:迭代法
思路
迭代法本质上是对方法一的改写,思想是一致的。代码简洁但是没有方法一的基础会比较难理解
代码
java版本
/**
* 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 reverse(ListNode pre, ListNode cur){
if(cur == null) return pre; //迭代出口
ListNode temp = cur.next;
cur.next = pre;
//下一次迭代入参相当于把pre和cur都往后移一步
return reverse(cur,temp);
}
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
}
- 时间复杂度: O(n), 要递归处理链表的每个节点
- 空间复杂度: O(n), 递归调用了 n 层栈空间
python版本
# Definition for singly-linked list.
//节点定义
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
//翻转链表
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
return self.reverse(head, None)
def reverse(self, cur: ListNode, pre: ListNode) -> ListNode:
if cur == None:
return pre
temp = cur.next
cur.next = pre
return self.reverse(temp, cur)
//测试程序
if __name__ == "__main__":
head = None
for i in[5,4,3,2,1]:
head = ListNode(i, head)
value1 = []
temp = head
while temp:
value1.append(temp.val)
temp = temp.next
print(f'原链表:{value1}')
print(head.val)
result = Solution().reverseList(head)
print(result.val)
value2 = []
temp = result
while temp:
value2.append(temp.val)
temp = temp.next
print(f'新链表:{value2}')
运行结果:
疑问??:
前面用java写递归时,要注意pre和cur的后移顺序
public ListNode reverse(ListNode pre, ListNode cur){
...
return reverse(cur,temp);
}
等价于
pre = cur;
cur=temp
即移动pre和 cur时,先移动pre 再移动cur,如果先移动cur会丢失pre的移动目标。
但是写python 时,经过测试运行代码这样写得不到正确结果,链表还是原链表结果。要反过来写。
java和python 形参赋值时,都是从左到右。所有这里没有想明白为什么不是一致的。