大家好,我是程序员学长~

1全文概览

2链表基础知识
链表的分类
单链表

双向链表

单向循环链表
单向循环链表是指首尾相连的单链表。

双向循环链表
双向循环链表是指首尾相连的双向链表。

链表的定义
#单链表
class ListNode(object):
def __init__(self, x):
#数据域
self.val = x
#指针域
self.next = None
链表的操作
单链表的删除操作

向单链表中增加一个节点

解题有妙招
引入哑结点

双指针法
3反转链表
LeetCode206. 反转链表
问题描述
给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。
示例:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
分析问题
首先,我们按照题目的要求,先把图画出来,然后再分析。

我们定义两个指针pre和cur。pre表示已反转部分的头结点,cur表示还没有反转部分的头结点。开始时cur=head,pre=None。
每次让cur->next=pre,实现一次局部反转。 局部反转完成后,cur和pre同时向前移动一位。 循环上述过程,直到链表反转完成。

代码实现
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def reverse(self, head):
cur = head
#初始化时,pre为None
pre = None
while cur:
next=cur.next
cur.next = pre
pre = cur
cur = next
return pre
head=ListNode(1,None)
cur=head
for i in range(2,6):
tmp=ListNode(i,None)
cur.next=tmp
cur=cur.next
s=Solution()
pre=s.reverse(head)
while pre!=None:
print(pre.val)
pre=pre.next
4合并两个有序链表
LeetCode21. 合并两个有序链表
问题描述
示例:
输入: {1,3,5},{2,4,6}
返回值: {1,2,3,4,5,6}
分析问题




我们下面来看一下代码实现。
def mergeTwoLists(self, l1, l2):
#合并后链表的哨兵结点
head=ListNode(-1,None)
pre=head
#循环遍历,将两个链表中的较小值插入到合并后的链表中
while l1 and l2:
if l1.val <= l2.val:
pre.next=l1
l1=l1.next
else:
pre.next=l2
l2=l2.next
pre=pre.next
#将剩余的非空链表插入到合并链表的后面
if l1:
pre.next=l1
else:
pre.next=l2
return head.next
其实,我们这里也可以使用递归的方式来实现。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def mergeTwoLists(self, l1, l2):
#链表l1为空,不需要合并,直接返回l2
if(l1==None):
return l2
#同理,l2为空,直接返回l1即可
if(l2==None):
return l1
if(l1.val<=l2.val):
l1.next=self.mergeTwoLists(l1.next,l2)
return l1
else:
l2.next=self.mergeTwoLists(l1,l2.next)
return l2
问题升级
LeetCode23. 合并K个升序链表
指向主链表中下一个结点的指针。 指向以此结点为头的链表。
示例如下所示:
4 -> 9 -> 15 -> 19
| | | |
7 13 18 28
| | |
8 21 37
|
20
实现函数flatten(),该函数用来将链表扁平化成单个链表。例如上面的链表,输出链表为
4 -> 7 -> 8 -> 9 -> 13 -> 15 -> 18 ->19 -> 20 -> 21 -> 28 -> 37
4 -> 9
| |
7 13 旋转一下 4 -> 7 -> 8 -> 20
| ----------> |
8 9 -> 13
|
20

下面我们来看一下代码如何实现。
class ListNode:
def __init__(self, val=0, right=None, down=None):
self.val = val
self.right = right
self.down = down
class Solution:
def mergeTwoLists(self, l1, l2):
#如果有一个链表为空,则合并后的链表就是另外一个
if(l1==None):
return l2
if(l2==None):
return l1
if(l1.val<=l2.val):
l1.down=self.mergeTwoLists(l1.down,l2)
return l1
else:
l2.down=self.mergeTwoLists(l1,l2.down)
return l2
def flatten(self,root):
if root== None or root.right == None:
return root
#把root->right 看作是已经有序的单链表,
#然后通过递归来进行归并
return self.mergeTwoLists(root, self.flatten(root.right))
5链表中的节点每k个一组翻转
问题描述
LeetCode25. K 个一组翻转链表
例如:
给定的链表是:1 -> 2 -> 3 -> 4 -> 5
对于 k=2,你应该返回 2 -> 1 -> 4 -> 3 -> 5
对于 k=3, 你应该返回 3 -> 2 -> 1 -> 4 -> 5
分析问题
我们把这个问题进行拆分。
我们首先将链表按照k个一组进行分组。对于最后一组,有可能元素个数不满足k个。 对于每一个分组,我们去判断元素的个数是否为k,如果是k的话,我们进行反转,否则不需要进行反转。

我们下面来看一下代码实现。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
#反转链表,并且返回链表头和尾
def reverse(self, head, tail):
prev = tail.next
p = head
while prev != tail:
next = p.next
p.next = prev
prev = p
p = next
return tail, head
def reverseKGroup(self, head, k):
#初始化一个哨兵节点,避免临界条件复杂的判断
prehead = ListNode(0)
#哨兵节点指向头结点
prehead.next = head
pre = prehead
while head:
tail = pre
#查看剩余部分长度是否大于等于k
for i in range(k):
tail = tail.next
#如果剩余长度小于k,则不需要反转,直接返回
if not tail:
return prehead.next
#tail指向子链表的尾部
#所以next指向下一个子链表的头部
next = tail.next
#将链表进行反转,并返回链表头和尾
head, tail = self.reverse(head, tail)
#把子链表重新接回原链表
pre.next = head
tail.next = next
pre = tail
head = tail.next
return prehead.next
6判断链表是否有环
问题描述
LeetCode141. 环形链表
如果链表中存在环,则返回 true 。否则,返回 false 。
示例:
输入:head = [-1,-7, 7,-4, 9, 6, -5, -2], pos = 3
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
分析问题
def hasCycle(self, head):
tags = set()
while head:
#表示已经被访问过了,代表有环
if head in tags:
return True
tags.add(head)
head = head.next
return False
我们可以知道该算法的时间复杂度和空间复杂度都是O(n)。那我们有更好的解法吗?
优化
你可以这么思考,当有两名同学在操场上以不同的速度进行跑步,他们最终肯定会相遇。因为操场是环形的,如果在直线上跑,那他们肯定不会相遇。
我们假设同学1以速度1在跑,同学2以速度2在跑。






下面我们来看一下代码如何实现。
def hasCycle(self, head):
#如果链表为空或者链表只有一个结点
#直接返回false,因为不可能有环
if not head or not head.next:
return False
#快慢指针
slow = fast = head
start = True
while slow != fast || start:
start=False
if not fast or not fast.next:
return False
slow = slow.next
fast = fast.next.next
return True
我们这里引入了一个变量start表示是否是起跑。
可以看到该算法的空间复杂度降低为O(1)。
7链表中环的入口结点
问题描述
LeetCode 剑指 Offer II 022. 链表中环的入口节点
给定一个链表,返回链表开始入环的第一个节点。如果链表无环,则返回 null。
说明:不允许修改给定的链表。
分析问题
def EntryNodeOfLoop(self, pHead):
tags = set()
while pHead:
#表示已经被访问过了,代表有环
if pHead in tags:
return pHead
tags.add(pHead)
pHead = pHead.next
return None
我们可以看到该算法的时间复杂度和空间复杂度都是O(n)。
优化

下面,我们来看一下代码实现。
def detectCycle(head):
if not head:
return None
#快慢指针
slow = head
fast = head
while True:
if not fast or not fast.next:
return None
fast=fast.next.next
slow=slow.next
#相遇时,跳出循环
if fast == slow:
break
ptr = head
while ptr != slow:
ptr=ptr.next
slow=slow.next
return ptr
该算法的时间复杂度是O(n),空间复杂度是O(1)。
8删除链表倒数第n个节点
问题描述
LeetCode 剑指 Offer II 021. 删除链表的倒数第 n 个结点
给定一个链表,删除链表的倒数第 n个结点,并且返回链表的头结点。
示例:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

分析问题



下面我们来看一下代码的实现。
def removeNthFromEnd(self,head,n):
#左右指针指向头结点
slow = fast = head
#fast先走n步
while n>0 and fast:
fast = fast.next
n=n-1
if not fast:
return head.next
while fast.next:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return head
该算法只遍历一遍链表,所以时间复杂度是O(n),空间复杂度是O(1)。
9两个链表的第一个公共结点
问题描述
LeetCode 剑指 Offer 52. 两个链表的第一个公共节点
要求:空间复杂度是O(1),时间复杂度是O(m+n)。
示例:

分析问题
def getIntersectionNode(headA,headB):
nodes=set()
while headA:
nodes.add(headA)
headA=headA.next
while headB:
if nodes.__contains__(headB):
return headB
headB=headB.next
return None

def getIntersectionNode(headA,headB):
p1 = headA
p2 = headB
while p1 != p2:
if p1:
p1=p1.next
else:
p1=headB
if p2:
p2=p2.next
else:
p2=headA
return p1
10两个链表生成相加链表
问题描述
LeetCode 剑指 Offer II 025. 链表中的两数相加
示例:
输入:[9,3,7],[6,3]
返回值:{1,0,0,0}
分析问题

要想让两个链表右端对齐,我们有两种实现方式。
将两个链表进行反转,然后直接求和。 借助栈这种先进后出的特性,来实现链表的右端对齐。
我们先来看一下如何使用链表反转来实现。
class Solution(object):
def reverse(self, head):
cur = head
#初始化时,pre为None
pre = None
while cur:
next=cur.next
cur.next = pre
pre = cur
cur = next
return pre
def addTwoNumbers(self, l1, l2):
#将两个链表翻转
l1 = self.reverse(l1)
l2 = self.reverse(l2)
head=ListNode(0)
pre=head
#代表是否进位
carray=0
while l1 or l2:
v1=l1.val if l1 else 0
v2=l2.val if l2 else 0
sum=v1+v2+carray
#进位数
carray=int(sum/10)
tmp=sum%10
node=ListNode(tmp)
pre.next=node
pre=pre.next
if l1:
l1=l1.next
if l2:
l2=l2.next
if carray==1:
node=ListNode(carray)
pre.next=node
return self.reverse(head.next)

def addTwoNumbers(l1, l2):
#申请两个栈
stack1=[]
stack2=[]
#l1入栈
while l1:
stack1.append(l1.val)
l1 = l1.next
while l2:
stack2.append(l2.val)
l2 = l2.next
head = None
carry = 0
while stack1 and stack2:
num = stack1.pop() + stack2.pop() + carry
#求进位数
carry=int(num/10)
tmp=num%10
node = ListNode(tmp)
node.next = head
head = node
s = stack1 if stack1 else stack2
while s:
num = s.pop() + carry
carry = int(num / 10)
tmp = num % 10
node = ListNode(tmp)
node.next = head
head = node
if carry==1:
node = ListNode(carry)
node.next = head
head = node
return head
11单链表的排序
问题描述
LeetCode 148. 排序链表
给定一个节点数为n的无序单链表,对其按升序排序。
要求:空间复杂度 O(n),时间复杂度 O(nlogn)。
示例:
输入:[-1,0,-2]
返回值:{-2,-1,0}
分析问题
分割环节
找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针法,快指针每次移动2步,慢指针每次移动1步,当快指针走到链表的末尾时,慢指针恰好指向了链表的中点位置。 找到中点后,将链表在中点处分割成两个子链表。
然后递归的进行分割,直到分割后的链表只有一个节点或者为Null。这时,分割的子链表都是有序的,因为只包含一个节点。 合并环节
将两个有序的链表合并成一个有序链表。我们可以采用双指针法求解。 递归执行,直到合并完成。

class Solution:
def sortList(self, head):
#如果链表为空或者只包含一个节点,递归终止
if not head or not head.next:
return head
#使用快慢指针法来寻找链表的中点
slow=head
fast=head.next
while fast and fast.next:
fast=fast.next.next
slow=slow.next
#slow指向的就是链表的中点,将链表在中点处进行分割
head2=slow.next
slow.next=None
#递归的切割分割链表
left = self.sortList(head)
right = self.sortList(head2)
#合并链表,使用双指针法
tmp = res = ListNode(0)
while left and right:
if left.val < right.val:
tmp.next=left
left=left.next
else:
tmp.next=right
right=right.next
tmp=tmp.next
if left:
tmp.next=left
else:
tmp.next=right
return res.next
优化
我们也可以采用自底向上的方法来求解。
首先,我们求出链表的长度length。然后将链表拆分成子链表进行合并。
我们用sublength表示每次需要排序的子链表的长度,初始时sublength=1。 每次将链表拆分成若干个长度为sublength的子链表(最后一个子链表的长度可能小于sublength),按照每两个子链表一组进行合并,合并后即可以得到若干个长度为sublength * 2的有序子链表(最后一个子链表的长度可能小于sublength * 2)。
将sublength的值加倍,重复第二步,然后对更长的有序子链表进行合并,直到有序子链表的长度大于或等于链表的长度,这样整个链表的排序就完成了。

我们来看一下代码的实现。
class Solution:
def sortList(self, head):
#合并两个有序链表
def merge(head1, head2):
#哨兵节点
dummyHead = ListNode(0)
temp=dummyHead
while head1 and head2:
if head1.val <= head2.val:
temp.next = head1
head1 = head1.next
else:
temp.next = head2
head2 = head2.next
temp = temp.next
if head1:
temp.next = head1
else:
temp.next = head2
return dummyHead.next
#如果链表为空,直接返回
if not head:
return head
#遍历一遍链表,求出链表的长度
length = 0
node = head
while node:
length += 1
node = node.next
#创建一个哨兵节点,指向链表头
dummyHead = ListNode(0)
dummyHead.next=head
#初始时,子链表的长度为1
subLength = 1
while subLength < length:
prev=dummyHead
cur=dummyHead.next
while cur:
#截取长度为subLength的子链表head1
head1 = cur
for i in range(1, subLength):
if cur.next:
cur = cur.next
else:
break
head2 = cur.next
cur.next = None
#截取长度为subLength的子链表head2
cur = head2
for i in range(1, subLength):
if cur and cur.next:
cur = cur.next
else:
break
#截取完后剩余的链表节点
surplus_head = None
if cur:
surplus_head = cur.next
cur.next = None
#将两个有序链表进行合并
merged = merge(head1, head2)
#将排好序的链表插入到新生成的链表里
prev.next = merged
#将指针移动到链表的末尾
while prev.next:
prev = prev.next
#继续合并剩余的节点
cur=surplus_head
subLength = subLength * 2
return dummyHead.next
该算法的时间复杂度是O(nlogn),空间复杂度是O(1)。
12判断一个链表是否为回文结构
问题描述
LeetCode 剑指 Offer II 027. 回文链表
给定一个链表,请判断该链表是否为回文结构。回文是指该字符串正序逆序完全一致。
示例:
输入:{1,2,2,1}
输出:true
说明:1 -> 2 -> 2 -> 1
分析问题
快慢指针寻找链表中点。

对链表的后半部分进行翻转

前半部分和后半部分进行比较。

class Solution:
def isPalindrome(self, head) -> bool:
#链表为空,直接返回true
if head is None:
return True
#找到链表的中点
middle_point = self.middle_point(head)
second_start = self.reverse_list(middle_point.next)
#判断前半部分和后半部分是否相等
result = True
first = head
second = second_start
while result and second is not None:
if first.val != second.val:
result = False
first = first.next
second = second.next
#还原链表并返回结果
middle_point.next = self.reverse_list(second_start)
return result
#快慢指针寻找中点
def middle_point(self, head):
fast = head
slow = head
while fast.next is not None and fast.next.next is not None:
fast = fast.next.next
slow = slow.next
return slow
#翻转链表
def reverse_list(self, head):
previous = None
current = head
while current is not None:
next_node = current.next
current.next = previous
previous = current
current = next_node
return previous
13链表内指定区间反转
问题描述
LeetCode 92. 反转链表 II
示例:
输入:head = [1,2,3,4,5], left = 2,right = 4
输出:[1,4,3,2,5]

分析问题
对于链表相关的题目,我们一定要先想清楚思路,搞懂指针移动的先后顺序。

具体来说,我们定义三个指针pre、cur、next变量。
我们首先将pre移动到第一个要反转的节点的前面,即pre->next=left。 然后将指针cur移动到第一个要反转的节点位置上,即cur=left。 然后将 cur->next 赋值给变量next。 将cur的下一个节点指向next的下一个节点,即cur->next=next->next。 然后将next的下一个节点指向pre的下一个节点,即next->next=pre->next。 然后将next插入到链表头部,即pre->next=next。 然后循环往复执行3、4、5、6,直到反转完链表区间内的节点。

下面我们来看一下代码如何实现。
class Solution:
def reverseBetween(self, head, left, right):
#设置哨兵节点,对于链表相关的问题,我们通过设置哨兵节点
#可以省去边界条件的判断
dummynode = ListNode(-1)
#哨兵节点指向链表头
dummynode.next = head
pre = dummynode
#遍历,使得pre指向链表反转部分
#的第一个结点left
for _ in range(left - 1):
pre = pre.next
#cur指向链表反转部分的第一个节点
cur = pre.next
for _ in range(right - left):
next = cur.next
cur.next = next.next
next.next = pre.next
pre.next = next
return dummynode.next
14删除有序链表中重复的元素-I
问题描述
LeetCode 83. 删除排序链表中的重复元素
示例:
输入:{1,1,2}
输出:{1,2}
分析问题

下面我们来看一下代码的实现。
class Solution:
def deleteDuplicates(self, head):
#如果链表为空,直接返回
if not head:
return head
#cur指向头结点
cur = head
#当cur.next不为空时
while cur.next:
#如果值相同,删除cur.next节点
if cur.val == cur.next.val:
cur.next = cur.next.next
#否则cur后移
else:
cur = cur.next
return head
该算法的时间复杂度是O(N),空间复杂度是O(1)。
15删除有序链表中重复的元素-II
问题描述
LeetCode 82. 删除排序链表中的重复元素 II
示例:
输入:head = [1,2,3,3,4,5]
输出:[1,2,5]

分析问题


下面我们来看一下代码的实现。
class Solution:
def deleteDuplicates(self, head):
#如果链表为空,直接返回
if not head:
return head
#创建一个哨兵节点
dummy = ListNode(0)
dummy.next=head
#开始时,将cur指向哨兵节点
cur = dummy
while cur.next and cur.next.next:
#如果cur.next.val和cur.next.next.val相同,删除重复元素
if cur.next.val == cur.next.next.val:
data = cur.next.val
while cur.next and cur.next.val == data:
cur.next = cur.next.next
else:
cur = cur.next
return dummy.next
该算法的时间复杂度是O(n),空间复杂度是O(1)。
16链表的奇偶重排
问题描述
LeetCode 328. 奇偶链表
示例:
输入:{1,2,3,4,5,6}
输出:{1,3,5,2,4,6}
分析问题
更新奇数节点,我们令 odd.next=even.next,此时奇数链表的最后一个节点指向了偶数节点even的后一个节点。然后令odd=odd.next,此时odd变成了even的后一个节点。
更新偶数节点,我们令 even.next=odd.next,此时偶数节点的最后一个节点指向了奇数节点odd的后一个节点。然后令even=even.next,此时even变成了odd的后一个节点。



下面我们来看一下代码的实现。
class Solution:
def oddEvenList(self, head):
#如果链表为空,直接返回
if not head:
return head
#evenHead指向偶数链表的头节点
evenHead = head.next
#指向奇数链表和偶数链表的末尾节点
odd = head
even = evenHead
while even and even.next:
#奇数链表的末尾节点指向偶数节点的下一个节点
odd.next = even.next
#奇数链表末尾节点后移
odd = odd.next
#偶数链表的末尾节点指向奇数节点的下一个节点
even.next = odd.next
#偶数链表末尾节点后移
even = even.next
#偶数链表连接到奇数链表的末尾
odd.next = evenHead
return head
该算法的时间复杂度是O(n),空间复杂度是O(1)。
17重排链表
问题描述
LeetCode 143. 重排链表
示例:
输入:head = [1,2,3,4]
输出:[1,4,2,3]
分析问题
首先,我们来观察一下链表重置前和重置后的变化。如下图所示:

找到原链表的中点,将链表分割成左右两部分。 对后半部分进行反转。 将原链表的左半部分和反转后的右半部分进行合并。
class Solution:
def reorderList(self, head):
#如果链表为空,直接返回
if not head:
return
#寻找链表的中点,将链表分割成左右两部分
mid = self.middleNode(head)
left = head
right = mid.next
mid.next = None
#对右半部分的链表进行反转
right = self.reverseList(right)
#左右链表进行合并
self.mergeList(left, right)
def middleNode(self, head):
#使用快慢指针法求中点
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
return slow
#对链表进行反转
def reverseList(self, head):
prev = None
curr = head
while curr:
tmp = curr.next
curr.next = prev
prev = curr
curr = tmp
return prev
#对两个链表进行合并操作
def mergeList(self, l1, l2):
#l1和l2的节点数量相差不会超过一个
#所以直接合并就好
while l1 and l2:
tmp1 = l1.next
tmp2 = l2.next
l1.next = l2
l1 = tmp1
l2.next = l1
l2 = tmp2
该算法的时间复杂度是O(N),空间复杂度是O(1)。
18链表中倒数最后k个节点
问题描述
LeetCode 剑指 Offer 22. 链表中倒数第k个节点
示例:
输入:{1,2,3,4,5},2
输出:{4,5}
分析问题

下面我们来看一下代码的实现。
class Solution:
def FindKthToTail(self, pHead, k):
#快慢指针同时执行头结点
slow=fast=pHead
#快指针移动k步
for i in range(0,k):
#如果还没走完k步就走到了链表尾,直接返回None
if not fast:
‘return None
fast = fast.next
#两个指针同时移动,直到fast为空
while fast:
slow=slow.next
fast=fast.next
return slow
19划分链表
问题描述
LeetCode 面试题 02.04. 分割链表
示例:
输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
分析问题




下面我们来看一下代码实现。
class Solution(object):
def partition(self, head, x):
#创建两个哑结点
smallHead = smallTail = ListNode(0)
largeHead = largeTail = ListNode(0)
#遍历链表
while head:
#如果head.val<x,将当前节点插入small中,否则插入large中
if head.val < x:
smallTail.next=head
smallTail=smallTail.next
else:
largeTail.next=head
largeTail=largeTail.next
head=head.next
largeTail.next=None
#合并两个链表
smallTail.next=largeHead.next
return smallHead.next
该算法的时间复杂度是O(n),空间复杂度是O(1)。
最后
欢迎关注公众号 程序员学长,助你少走弯路进大厂。
你知道的越多,你的思维越开阔。我们下期再见。




