链表问题汇总
- 1 前言
- 2 题目
-
- 2.1 链表中倒数第k个结点
-
- 2.1.1 题目
- 2.1.2 思路
- 2.1.3 代码
- 2.2 反转链表
-
- 2.2.1 题目
- 2.2.2 思路
- 2.2.3 代码
- 2.3 合并两个排序的链表
-
- 2.3.1 题目
- 2.3.2 思路1
- 2.3.3 代码1
- 2.3.4 思路2
- 2.3.5 代码2
- 2.4 两个链表的第一个公共结点
-
- 2.4.1 题目
- 2.4.2 思路
- 2.4.3 代码
- 2.4.4 图解
- 2.5 复杂链表的复制
-
- 2.5.1 题目
- 2.5.2 思路
- 2.5.3 图解
- 2.5.4 代码
- 2.6 链表中环的入口结点
-
- 2.6.1 题目
- 2.6.2 思路
- 2.6.3 图解
- 2.6.4 代码
- 2.7 删除链表中重复的结点
-
- 2.7.1 题目
- 2.7.2 思路1
- 2.7.3 代码1
- 2.7.4 思路2
- 2.7.5 代码2
- 参考
1 前言
整理下剑指offer链表问题。
2 题目
2.1 链表中倒数第k个结点
2.1.1 题目
输入一个链表,输出该链表中倒数第k个结点。
2.1.2 思路
思路:
- 如果输出链表正向的第k个结点会吗?应该会的!那么再得到长度,一减,就ok了啊!
- 但是上面思路太麻烦,有一个非常牛逼的思路!
- 牛逼思路:我们可以定义两个指针。
- 第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动;
- 从第k步开始,第二个指针也开始从链表的头指针开始遍历。
- 由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
- 图示:
2.1.3 代码
- 代码写起来!
- 写代码发现有报错,考虑下边界条件:如果查询的是最后一个元素咋办?也就是倒数第一个!
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def FindKthToTail(self, head, k):
# write code here
fast = slow = head
'''
为什么这种思路ok?
因为是循环k次,也就是距离为k 这时候就不能以.next作为循环终止条件了!而是自己为终止条件!但还是下面一种好思考一些!
'''
for _ in range(k):
if not fast: # 如果fast为空 返回空
return None
fast = fast.next
while fast:
slow, fast = slow.next, fast.next
return slow
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def FindKthToTail(self, head, k):
# write code here
# 边界条件!
if head == None or k == 0:
return None
# 初始化指针
phead = head
pbehind = head
# 先走k-1步
for i in range(k-1):
if phead.next == None: # 防止出现链表就1个元素 然后说倒数第二个元素 这样就gg
return None
else:
phead = phead.next
while phead.next != None: # 只要走在前面的指针的下一步不是指向None 那么就继续移动!
phead = phead.next
pbehind = pbehind.next
return pbehind
注意return的结果!!!不要把元素搞出来!看题目的需求!!!是返回节点!!!现在直接返回节点就ok!所以审题相当重要!
2.2 反转链表
2.2.1 题目
输入一个链表,反转链表后,输出新链表的表头。
2.2.2 思路
思路:
- 如何反转呢?也就是把元素全部倒置过来!如何实现?
- 能不能依次存储到一个list 然后反转list?好像不太行吧?因为最后要输出表头诶?就是直接输出一个节点!
- 我好像只要找到新链表的表头就ok了?所以不需要存储?那直接遍历到最后一个输出?不行的!输出就是反转的链表!
代码思路:
- 每一步解释下面都有!
- 值得注意的就是 最后返回的是pre!
- 一开始pre的定义是None!因为首节点反转之后要指向None!巧妙!
- 循环体部分包括两部分:
- 转换节点方向【先保存下一个指向信息 再转向】
- 移动指针【直接前一个指向后一个 后一个指向之前保存的next!】
- 注意考虑特殊情况
- 空链表
- 只有1个节点的链表的情形!
2.2.3 代码
代码:
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
# write code here
if pHead == None:
return None
# 定义两个指针
pre = None #前一个节点 因为头节点反转后需要指向None
cur = pHead # 当前节点
# 开始循环
while cur != None:
# 先保留当前节点的下一个信息 防止链表断裂
next = cur.next
# 让当前节点指向前一个节点
cur.next = pre
# 移动指针
pre = cur
cur = next
return pre
2.3 合并两个排序的链表
2.3.1 题目
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
2.3.2 思路1
思路:
- 如何合并呢?两个排序的链表?是否要比较链表内元素的大小?关键应该还是指向的问题!
- 如果逐个元素两两比较是不是太麻烦了?应该有其余的方法!在两个链表上分别设指针?思路没问题!但是稍微修改下!
代码思路:
- 非递归版本
- 一开始先定义一个空的节点!然后让pHead指向它!这样就能很好地解决最后不知道返回谁的问题!直接返回pHead.next 逐个打印出来就行了!
- 中间的判断循环语句和自己一开始的思路一致 不难!谁元素小指向谁!并且它还要移动!
- 最后需要做一个判断的就是 有可能一个链表中元素为空了!那么这时候全部指向另一个就ok!
- 中间有一个细节就是
具体原因上面解释了!tmp = tmp.next
- 最后返回的是
记住这个trick!pHead.next
2.3.3 代码1
非递归版本 代码:
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回合并后列表
def Merge(self, pHead1, pHead2):
# write code here
# 先考虑特殊情况
#初始化
tmp = ListNode(0)
pHead = tmp # pHead指向tmp
# 开始循环
while pHead1 and pHead2: # 均非空
if pHead1.val < pHead2.val: # 如果1的元素小于2的 改变1
tmp.next = pHead1 # 让初始tmp指向1
pHead1 = pHead1.next # 移动1
else: # 如果1的元素大于2的 改变2
tmp.next = pHead2 # 让初始tmp指向2
pHead2 = pHead2.next # 移动2
tmp = tmp.next # 同时tmp每一次循环结束后都要向前移动一次!因为之后都要指向新元素 所以要移动到目前的位置!
# 循环结束 看哪一个为空
if not pHead1: # 如果pHead1为空 那么tmp直接下一步指向2
tmp.next = pHead2
if not pHead2: # 如果pHead2为空 那么tmp直接下一步指向1
tmp.next = pHead1
return pHead.next # 最后返回谁呢?为什么返回pHead.next?可以一个一个的打印出来!
2.3.4 思路2
递归版本图解:
2.3.5 代码2
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回合并后列表
def Merge(self, pHead1, pHead2):
# write code here
# 先考虑特殊情况
if pHead1 is None:
return pHead2
if pHead2 is None:
return pHead1
# 开始循环递归判断!
if pHead1.val < pHead2.val: # 1元素小于2
pHead1.next = self.Merge(pHead1.next,pHead2) # 让1指向除了1+2的新的两个链表
return pHead1 # 最后返回pHead1?妙!
else:
pHead2.next = self.Merge(pHead1,pHead2.next)
return pHead2
代码思路:
- 这个递归版本思路太妙了!
- 怎么想呢?就是说每次比较两个元素之后,不局限于元素之间的指向转变!而是直接将小的元素指向一个新的两个链表!递归实现!
以[1 3 ] [2 4 ] 来示例:
进入循环:
pHead1.next = self.Merge(pHead1.next,pHead2)=[3,5],[2,4,6]
= 进入第二个循环
pHead2.next = self.Merge(pHead1,pHead2.next)=[3,5],[4,6]
= 进入第一个循环
pHead1.next = self.Merge(pHead1.next,pHead2)=[5],[4,6]
= 进入第二个循环
pHead2.next = self.Merge(pHead1,pHead2.next)=[5],[6]
= 进入第一个循环
pHead1.next = self.Merge(pHead1,pHead2.next)=[None],[6]
= 6
- 直接从图形角度理解吧!逆推推不回去!就想着每次会多出一个元素出来!
- return 1 还是 2 取决于第一次进入的是哪个部分里面!不要过多纠结于每一步的细节!从图形角度理解就ok!
2.4 两个链表的第一个公共结点
2.4.1 题目
输入两个链表,找出它们的第一个公共结点。
2.4.2 思路
思路:
- 直接搜索!要不要先排序呢?然后二分查找?但是排序的复杂度会比较高吧?是的!
- 先暴力搜索!先固定一个链表的一个元素,然后遍历另外一个链表,再换另一个元素!
写了代码,但是测试用例有点一脸懵逼,
测试用例说明这个题目的意思是 公共节点之后的元素都一样!
a = {1,2,3,6,7}
b = {4,5,6,7}
图解就是:
这时候采用一种思路:
- 先让把长的链表的头砍掉,让两个链表长度相同,这样,同时遍历也能找到公共结点。此时,时间复杂度O(m+n),空间复杂度为O(MAX(m,n))。
2.4.3 代码
代码为:
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def FindFirstCommonNode(self, pHead1, pHead2):
# write code here
# 下面这个没问题 先判断特殊情况
if pHead1 == None or pHead2 == None:
return None
cur1, cur2 = pHead1, pHead2
while cur1 != cur2:
cur1 = cur1.next if cur1 != None else pHead2
cur2 = cur2.next if cur2 != None else pHead1
return cur1
这种做法代码看起来其实有点炫,我们来分解一下,如何让两个长度不同的链表并驾齐驱的?
2.4.4 图解
其实四张图就可以解释!图解如下:
2.5 复杂链表的复制
2.5.1 题目
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
2.5.2 思路
思路:
- 完全没有思路,啥意思?复制链表?咋复制?而且这个链表还很奇怪,两个指向,一个是下一个节点,一个是任意一个节点?蜜汁操作!什么叫复制?以及返回head头结点?
思路还是比较6的:
我们这里将复杂链表的复制过程分解为三个步骤。在写代码的时候我们每一步定义一个函数,这样每个函数完成一个功能,整个过程的逻辑也就非常清晰明了了。
我们这里采用三步:
- 第一步:复制复杂指针的label和next。但是这次我们把复制的结点跟在元结点后面,而不是直接创建新的链表;
- 第二步:设置复制出来的结点的random。因为新旧结点是前后对应关系,所以也是一步就能找到random;
- 第三步:拆分链表。奇数是原链表,偶数是复制的链表。
2.5.3 图解
图解为:
2.5.4 代码
代码为:
# -*- coding:utf-8 -*-
# class RandomListNode:
# def __init__(self, x):
# self.label = x
# self.next = None
# self.random = None
class Solution:
# 返回 RandomListNode
def Clone(self, pHead):
# 边界条件:如果为空链表 返回空
if not pHead:
return None
# 首先定义一个指针
dummy = pHead
# first step, N' to N next 即 复制链表 紧跟它后面!
while dummy: # 爸爸非空即进入循环
# 1 记录爸爸的next指向
dummynext = dummy.next
# 2 记录爸爸基因(label),产生儿子
copynode = RandomListNode(dummy.label)
# 3 让儿子指向爸爸的next指向
copynode.next = dummynext
# 4 让爸爸指向儿子
dummy.next = copynode
# 5 让爸爸继续向前 指向本应该的next
dummy = dummynext
# 复制完成 重新归一化dummy
dummy = pHead
# second step, random' to random' 即确定random的指向
while dummy:
# 1 先记录父亲的random位置
dummyrandom = dummy.random
# 2 让儿子指向爸爸的next位置
copynode = dummy.next
# 3 如果存在父亲的random位置就让儿子的random也复制一下
if dummyrandom:
copynode.random = dummyrandom.next
# 4 重点:如何让父亲继续走?让父亲指向儿子的next节点即可!
dummy = copynode.next
# third step, split linked list 分开list 奇数为真实 偶数为复制的!即如何返回copynode
# 其实就是确定新的指向!新的链表指向顺序!
# 复制random完成 重新归一化dummy
dummy = pHead
# 爸爸的next指向儿子头结点copyHead!
copyHead = pHead.next
while dummy:
# 只要爸爸不为空 进入循环!
# 1 产生儿子-爸爸dummy的next
copyNode = dummy.next
# 2 dummynext记录爸爸实际的next指向-copyNode.next
dummynext = copyNode.next
# 3 让爸爸跳到实际的next指向上
dummy.next = dummynext
# 4 如果dummynext非空 那么儿子的下一个指向为dummynext的next指向
if dummynext:
copyNode.next = dummynext.next
else:
copyNode.next = None
# 5 让父亲指向实际的下一个位置 继续循环!
dummy = dummynext
return copyHead
2.6 链表中环的入口结点
2.6.1 题目
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
2.6.2 思路
思路:
- 遍历链表!
- 什么情况下会包括环呢?也就是某一个节点的next指向前面的任一节点!这时候输出该节点?
- 如何判断某一个节点的next指向前面的任一节点?其实指向后面的节点也ok!就是不指向下一个!如何判断呢?好难啊!不好想~
牛逼的思路:
- 分为两部分。一个结论+如何确定链表环中元素的个数!
- 结论:如果一个链表有环,且环中元素的个数为n,那么定义两个指针,一个指针先走n步,另一个指针在原点位置,然后同时开始移动两个指针,直至两个指针相遇,相遇的元素即为环的入口节点!
2.6.3 图解
具体见下图:
- 如何确定环内元素的个数呢?使用**快慢指针**!定义两个指针,一前一后,一个移动2步,一个移动1步,两个指针肯定会相遇,具体见下图:
- 相遇之后只使用一个指针开始继续向前走,一直到又走到这个位置的时候,记录下走了多少次,即为环内元素个数!
- 如果判断没有环呢?如何判断?那就是快慢指针的时候没有相遇!此时就没有环了!
2.6.4 代码
下面开始写代码:
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def EntryNodeOfLoop(self, pHead):
# write code here
if not pHead:
return None
# 1 首先定义快慢指针 确定是否有环以及环内元素个数
# 1.1 定义指针 注意一开始要选择不同的
slow = pHead
fast = pHead.next
# 1.2 快慢指针开始遍历 直至相遇
while slow != fast:
# 注意考虑边界情况!就是只有一个节点!肯定也不会有环!
if slow.next == None or fast.next == None:
return None
else:
# 慢指针一次走一步 快指针一次两步
slow = slow.next
fast = fast.next.next
# 1.3 相遇 用一个指针去走 直至回来
tmp = slow
n = 1
while tmp.next != slow:
n += 1
tmp = tmp.next
# 1.4 确定了环内元素个数
# 2 再定义两个指针!确定入口结点
# 2.1 定义两个指针
cur1 = pHead
cur2 = pHead
# 2.2 先移动cur2往前n步
for i in range(n):
cur2 = cur2.next
# 2.3 现在开始同步移动 直至两者相等
while cur1 != cur2:
cur1 = cur1.next
cur2 = cur2.next
return cur1
2.7 删除链表中重复的结点
2.7.1 题目
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
2.7.2 思路1
这个思路超牛逼!
- 先将每个元素过一遍!然后统计出现的次数!过滤掉元素大于1的!
- 然后再将剩余的元素组织成链表的形式!
- 完美!
2.7.3 代码1
思路比较清晰!开始写代码:
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteDuplication(self, pHead):
# write code here
res=[]
while pHead:
res.append(pHead.val)
pHead=pHead.next
res=list(filter(lambda c:res.count(c)==1,res))
newList=ListNode(0)
pre=newList
for i in res:
node=ListNode(i)
pre.next=node
pre=pre.next
return newList.next
2.7.4 思路2
思路:
- 已经排序了,那就直接遍历每一个元素吧!如果遇到相同的,都删了关键就是得确定指向的问题!所以应该至少要有2个指针!保留重复元素的最后一个元素的next指向,让首个重复元素前面元素指向该next指向即可!遇到重复的就一直往下走!直到不等于!while!
思路更新:
- 其实思路和自己想的基本差不多 只不过有些细节需要注意
- 首先得创建一个first,作为最初始!其指向head!
- 定义一个指针跟着pHead跑!因为要考虑其前面的元素!!!!
- 进入循环直接考虑两个元素是否相等,相等取出元素来进行遍历循环,然后让之前的前一个指针指向它,即在判断的时候只用自身以及next来判断就好!保持前指针last的纯粹性!
- 最后返回是谁也很重要!为什么不返回pHead?因为第一个元素无法显示!没有指向第一个元素的!而pHead是最后一个。为什么不返回last,因为没有指向第一个元素!只有first符合条件!
2.7.5 代码2
代码2:
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteDuplication(self, pHead):
# write code here
# 特殊情况
if not pHead:
return None
# 得定义一个指针跟着pHead跑!因为要考虑其前面的元素!
first=ListNode(-1)
first.next=pHead
last=first
while pHead and pHead.next:
# 开始一次判断
if pHead.val == pHead.next.val:
# 如果两个元素相等 则往后继续移动-取出值来 做判断依据
val = pHead.val
while pHead and pHead.val == val:
pHead = pHead.next
# 循环结束 说明此时跳出了重复元素 改变指向
last.next = pHead
else:
# 说明元素不等 继续往前移动
last = pHead
pHead = pHead.next
return first.next
参考
- https://blog.csdn.net/c406495762/article/details/79247243