链表
更多请查看我的专栏:LeetCode(力扣)刷题指南
可直接在
LeetCode中搜索题目名称
文章目录
1. 两数相加:链表
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
1.1 解决方案
算法思路:时间复杂度O(max(len(l1),len(l2)))即O(n)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
dummyNode=ListNode(0) #值不重要
l3=dummyNode
carry=0 #进阶初始化,carry=0或1
while(l1 or l2):
x=l1.val if l1 else 0 #如果l1的值为真,返回给x;否则x=0({},[],null,False,()这些数据都为假)
y=l2.val if l2 else 0 #为什么不是if l1.val或l2.val:如果是[1,8],[0]就会出错,l2的第二个结点根本不存在,无法判断
sum_l1l2=x+y+carry
carry=sum_l1l2//10
l3.next=ListNode(sum_l1l2%10) #这两行代码可以使用divmod()
l3=l3.next
if(l1 != None):l1=l1.next #不能判断l1.next!=None,否则l1.next永远不等于0,进入死循环
if(l2 != None):l2=l2.next
if(carry==1):
l3.next=ListNode(1)
return dummyNode.next
1.2 思考和总结
-
如何理解逆序?
用笔算,也是从个位数开始
-
如何理解dummyNode?
这是个哑结点,用来返回列表(链表)l3
-
如何理解carry?
9+9=18,而每个结点只能存储一个数,所以下一个结点要+carry(=1)
-
为什么是 if l1 else 0而不是if l1.val else 0?
其实l1是类的一个实例,特性有val,next,即使
l1.val=None、l1.next=None,l1仍然也为真。假设输入是[1,8], [0]。进入第二轮循环时,l2=None,此时l2没有特性.val,会出现AttributeError: 'NoneType' object has no attribute 'val’的错误。
2. 反转链表:递归
2.1 解决方案
1.利用外部空间
- 申请一个动态扩容的数组或者容器,比如
ArrayList这样的。然后不断遍历链表,将链表中的元素添加到这个容器中。 - 再利用容器自身的 API,反转整个容器,这样就达到反转的效果了。
最后同时遍历容器和链表,将链表中的值改为容器中的值。因为此时容器的值是:5 4 3 2 1,链表按这个顺序重新被设置一边,就达到要求啦。
当然你可以可以再新创建 N 个节点,然后再返回,这样也可以达到目的。
这种方式很简单,但你在面试中这么做的话,面试官100%会追问是否有更优的方式,比如不用外部空间。
2.双指针迭代:(时间O(n):112ms,空间O(n):30MB)
- 申请两个指针,第一个指针pre初始指向None;第二个指针cur初始指向head;
- 将cur的next指向pre,然后 pre 和 cur 前进一位。
- 迭代完(cur 变成 None),pre 就是最后一个节点了。
(迭代中,需要临时变量now来完成cur和pre的指向变化)

代码实现:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
#迭代
pre,cur=None,head
while(cur):
now=cur.next
cur.next=pre
pre=cur
cur=now
return pre
3.迭代:(时间O(n):112ms,空间O(1):33.7MB)
递归的两个条件:
- 终止条件是当前节点或者下一个节点==None
- 在函数内部,改变节点的指向,也就是 head 的下一个节点指向 head 递归函数:
head.next.next = head
其实就是 head 的下一个节点指向head。
递归函数中每次返回的 cur 其实只最后一个节点,在递归函数内部,改变的是当前节点的指向。
动画演示如下:

代码实现:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
#递归
if head==None or head.next==None:
return head
cur=self.reverseList(head.next)
head.next.next=head
head.next=None
return cur #对自身的调用
2.2 总结和思考
1.递归
函数实现的具体步骤:
#递归步骤
[1,2,3,4,5]
Step5.reverseList(1)>>>2.next=1,1.next=None,return 1
^
^
^
Step4.reverseList(2)>>>3.next=2,2.next=None,return 2
^
^
^
Step3.reverseList(3)>>>4.next=3,3.next=None,return 3
^
^
^
Step2.reverseList(4)>>>5.next=4,4.next=None,return 4
^
^
^
Step1.reverseList(5)>>>return 5
3. K个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明 :
- 你的算法只能使用常数的额外空间。
- 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
3.1 解决方案
1.栈
用栈,我们把 k 个数压入栈中,然后弹出来的顺序就是翻转的!
这里要注意几个问题:
第一,剩下的链表个数够不够 k 个(因为不够 k 个不用翻转);
第二,已经翻转的部分要与剩下链表连接起来。
注意:这里的空间复杂度为O(k),并不符合题目要求
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
dummy = ListNode(0) #设置一个哑结点,初始化为链表的头结点
p = dummy
while True:
count = k
stack = [] #初始化栈
tmp = head #初始化:本个翻转区的第一个结点存放在临时结点中
while count and tmp: #将可翻转区的结点压栈
stack.append(tmp)
tmp = tmp.next
count -= 1
# 注意,目前tmp所在k+1位置,即下一个可翻转区的第一个结点
# 说明剩下的链表不够k个,跳出循环
if count :
p.next = head #本翻转区不可翻转,按顺序连链接
break
# 翻转操作
while stack:
p.next = stack.pop()
p = p.next
#与剩下链表连接起来
p.next = tmp #此时临时结点为该下一个翻转区的第一个结点
head = tmp #初始化初始结点
return dummy.next
2.尾插法
直接举个例子:k = 3
pre
tail head
dummy 1 2 3 4 5
# 我们用tail 移到要翻转的部分最后一个元素
pre head tail
dummy 1 2 3 4 5
cur
# 我们尾插法的意思就是,依次把cur移到tail后面
pre tail head
dummy 2 3 1 4 5
cur
# 依次类推
pre tail head
dummy 3 2 1 4 5
....
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
pre = dummy
tail = dummy
while True:
count = k
while count and tail:
count -= 1
tail = tail.next
if not tail: break #所剩元素不够翻转,跳出循环
head = pre.next #将初始结点设为翻转区翻转后的最后一个结点
while pre.next != tail:
cur = pre.next # 获取下一个元素
pre.next = cur.next # pre与cur.next连接起来,此时cur(孤单)掉了出来
cur.next = tail.next # 和剩余的链表连接起来
tail.next = cur #插在tail后面
# 改变 pre tail 的值
pre = head
tail = head
return dummy.next
3.递归算法
算法思路:
-
设置当前结点
cur为reverseKGroup(head,k)的head; -
循环(当前结点
cur为真和count!=k):当前结点cur=cur.next,计数器count+=1 -
如果
count==k:-
当前结点
cur=reverseKGroup(cur,k)(其中reverseKGroup(cur,k)中的cur为该层递归中可翻转区的第一个结点,即该层可翻转区的初始结点head)(目的:返回下层递归可翻转区的初始结点结点到当前结点cur中) -
循环(当
count为真):- 将翻转结点的下一个存放到临时结点中
tmp=head.next; - 将翻转结点指向当前结点
head.next=cur,实现翻转; - 将当前结点设为下一个被指向结点(即翻转结点)
cur=head; - 将临时结点中结点赋给翻转结点
head=tmp; - 计数器计数
count-=1
- 将翻转结点的下一个存放到临时结点中
-
将该层递归的初始结点设置当前结点(即上层递归中的第一个翻转结点)
head=cur
-
-
返回这个初始结点
return head(如果有上层递归,则该初始结点为上层递归中的第一个翻转结点;没有这是最上层递归,则是返回翻转后的链表)。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
cur = head
count = 0
while cur and count!= k:
cur = cur.next
count += 1
if count == k:
cur = self.reverseKGroup(cur, k)
while count:
tmp = head.next
head.next = cur
cur = head
head = tmp
count -= 1
head = cur
return head
3.2 思考与总结
1.另外一种思路:
在翻转链表的时候,可以借助三个指针:prev、curr、next,分别代表前一个节点、当前节点和下一个节点,实现过程如下所示。

-
将 curr 指向的下一节点保存到 next 指针;
-
curr 指向 prev,一起前进一步;
-
重复之前步骤,直到 k 个元素翻转完毕;
-
当完成了局部的翻转后,prev 就是最终的新的链表头,curr 指向了下一个要被处理的局部,而原来的头指针 head 成为了链表的尾巴。
本文详细解析了LeetCode中关于链表的几道经典题目,包括两数相加、反转链表以及K个一组翻转链表的多种解法,如递归、迭代、尾插法等。通过实例探讨了不同解法的时间复杂度和空间复杂度,并提供了代码实现。
经典题:链表&spm=1001.2101.3001.5002&articleId=104482934&d=1&t=3&u=5d2de6ef93b44c86b1610c28ec5f995b)

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



