《剑指offer》-数据结构篇-链表
题目
-
从尾到头打印链表
-
反转链表
-
链表中倒数第K个结点
-
合并两个/K个有序链表
-
复杂链表的复制
-
删除链表中的重复元素
-
两个链表的第一个公共结点
-
链表中环的入口结点
-
二叉搜索树与双向链表
代码实现
从尾到头打印链表
题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
思路:
-
方式1:先加载链表中的所有值到新建的列表中,然后将该列表反转,返回一个ArrayList。
-
方式2:借助辅助栈,一个栈存储链表中由前至后结点值,另一个栈存储该栈依次弹出结点值。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:# 返回从尾部到头部的列表值序列,例如[1,2,3]def printListFromTailToHead(self, listNode):# write code here"""#解法1:先加载链表中的所有值到列表中,然后将该列表反转,返回一个ArrayListret=[]head=listNodewhile(head):ret.append(head.val)head=head.nextret.reverse()return ret"""#解法2:利用辅助栈来解决stack = []result_array = []node_p = listNodewhile node_p:stack.append(node_p.val)node_p = node_p.next while stack:result_array.append(stack.pop(-1))return result_array
反转链表
题目描述:输入一个链表,反转链表后,输出新链表的表头。
思路:注意和第1题的区别,此题返回的是一个链表。设置None,从头指针开始依次改变指针的指向。注意:需用pHead.next作为判断条件,而非pHead,否则会导致头节点为 None,出现错误。反转链表很典型,一些链表的复杂题目是在其基础上产生的。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:# 返回ListNodedef ReverseList(self, pHead):# write code here#判断特殊情况#对于pHead的长度为1的情况依然是适用的if not pHead: return None pre = None #反转指针的指向#这里一定要是判断pHead.next,用pHead的话pHead的头节点是None,会出现问题while pHead.next:nex = pHead.next pHead.next = pre pre = pHead pHead = nex pHead.next = prereturn pHead
链表中倒数第K个结点
题目描述:输入一个链表,输出该链表中倒数第k个结点。
思路:采用快慢指针的方式。让一个指针先走k步,当该指针到达None时,另一个指针指向该链表中的倒数第k个结点,返回该指针所对应的结点值。快慢指针的方式的题目比较重要,很多链表题目都是使用这种方法求解的。
# -*- 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 not head or k<=0: return None#快慢指针的初始位置fast_p = headslow_p = head #确保可以得到与最初的fast_p位置相隔k个结点的新的新的fast_p位置#该位置也是和slow_p相距k个结点的位置for _ in range(k):if fast_p:fast_p = fast_p.nextelse:return None#由于快慢指针相距为k,设原链表的长度为n,则最终慢指针的位置为n-k+1#即倒数第k个结点的位置while fast_p:fast_p = fast_p.nextslow_p = slow_p.nextreturn slow_p #使用slow_p返回该节点的数值
合并两个/K个有序链表
题目一:合并两个有序链表
题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:构建新的列表存储两个单调递增的链表的数值,对列表进行排序生成新的列表后,再由其新的生成链表。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:# 返回合并后列表def Merge(self, pHead1, pHead2):# write code herep1 = pHead1p2 = pHead2#新建一个链表l = list() #替换为[]会导致执行的速度下降#加载链表的值到列表while p1 or p2:if p1:l.append(p1.val)p1 = p1.nextif p2:l.append(p2.val)p2 = p2.nextl.sort()#构建新的链表for i in range(len(l)):if i ==0:l3 = ListNode(l[i])p3 = l3else:p3.next = ListNode(l[i])p3 = p3.next#注意返回的是链表,而不是最后一个结点,因而用了l3return l3 if l else None
题目二:合并K个有序链表
题目:leetcode链接(https://leetcode-cn.com/problems/merge-k-sorted-lists/)
思路:分治递归,详见:合并K个有序链表
N是所有链表中元素的总数,K是链表数量,时间复杂度为NlogK,空间复杂度为logK
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:def mergeKLists(self, lists: List[ListNode]) -> ListNode: if len(lists) <= 2: return self.mergeTwoLists(lists)def splitLists(lists):idx = len(lists) // 2return lists[:idx], lists[idx:]a, b = splitLists(lists)a_merge = self.mergeKLists(a)b_merge = self.mergeKLists(b)return self.mergeTwoLists([a_merge, b_merge])def mergeTwoLists(self, lists):if not lists: return Noneif len(lists)==1: return lists[0]head1, head2 = listshead = dump = ListNode(0)while head1 and head2:if head1.val < head2.val:head.next = head1head1 = head1.nextelse:head.next = head2head2 = head2.nexthead = head.nexthead.next = head1 if head1 else head2return dump.next
复杂链表的复制
题目描述:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路:先复制按照指向正常结点的结点所构成的链表,再在基础上复制指向任意结点的结点所构成的链表,最终输出复杂链表。
# -*- coding:utf-8 -*-
# class RandomListNode:
# def __init__(self, x):
# self.label = x
# self.next = None
# self.random = None
class Solution:# 返回 RandomListNodedef Clone(self, pHead):# write code here#判断链表不为空if not pHead: return Nonep = pHeadnew_h = RandomListNode(p.label)pre_p = new_hrandom_map = {pHead: new_h}p = p.next#先完成对于链表的复制while p:new_p = RandomListNode(p.label)random_map[p] = new_ppre_p.next = new_ppre_p = pre_p.nextp = p.nextp = pHeadnew_p = new_h#然后完成对于自由结点的复制while p:random_p = p.randomif random_p:new_p.random = random_map[random_p]p = p.nextnew_p = new_p.next#返回复杂链表return new_h
删除链表中的重复元素
题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路:依次遍历链表中的数值,将不重复结点的指针指向下一个不重复的结点,最后记得输出整个链表而非得到的链表中的最后一个结点的数值。题目很典型,类似的题目是:对于链表中的重复结点,只保留一次数值。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:def deleteDuplication(self, pHead):# write code heredummy_head = ListNode(None) dummy_head.next = pHead pre = dummy_head cur = pHead while cur: if cur.next and cur.val == cur.next.val: while cur.next and cur.val == cur.next.val: cur = cur.next # cur.val != cur.next.val pre.next = cur.next cur = cur.next continue pre = pre.next cur = cur.next#要用.next return dummy_head.next
扩展题目:删除链表中的重复元素(只保留重复元素中的一个)
题目描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
思路:和第6题类似,只是指针指向的不同。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:def deleteDuplicates(self, head: ListNode) -> ListNode:""":type head: ListNode:rtype: ListNode"""cur = headwhile cur:while cur.next and cur.val == cur.next.val:cur.next = cur.next.nextcur = cur.next#注意返回headreturn head
两个链表的第一个公共结点
题目描述:输入两个链表,找出它们的第一个公共结点。
思路:
方法1:两个指针循环遍历链表,指针指向的数值相同时,说明是第一个公共结点。
方法2:找到两个链表的长度差k。让一个指针指向较长的链表的第k+1个结点,一个指针指向较短的链表的第一个结点,当指针指向的数值相同时,说明是第一个公共结点。
注意:题目描述中的两个链表A和B自公共结点L之后的结点都是一样的。
# -*- 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"""解决方案1#判断条件if pHead1 is None or pHead2 is None:return Nonepa = pHead1pb = pHead2 #对于两个链表,相交的第一个节点之后的部分也是相同的while pa != pb:if pa:pa = pa.nextelse:pa = pHead2if pb:pb = pb.nextelse:pb = pHead1#pa==pb对应的是相交的节点。注意这里是返回节点,因而也可以是返回pa。return pb"""#解决方法2:采用对其最后的结点对齐的方式,使得较长的链表的#头结点先领先两者的长度差大小的步数,之后指针指向相同的结点值时就找到了第一个公共结点p1 = pHead1p2 = pHead2n_p1 = 0n_p2 = 0while p1:p1 = p1.nextn_p1 += 1while p2:p2 = p2.nextn_p2 += 1if n_p1 < n_p2:pHead1, pHead2 = pHead2, pHead1for _ in range(n_p1 - n_p2):pHead1 = pHead1.nextwhile pHead1:if pHead1 == pHead2:return pHead1else:pHead1 = pHead1.nextpHead2 = pHead2.nextreturn None
链表中环的入口结点
题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路:利用快慢指针。刚开始指针都指向链表的第一个结点,先找到两个指针相遇的结点值。之后,根据链表的第一个结点到环入口的步数s等于相遇点到环入口的步数(r-m)加上环的长度r的整数倍进行while判断,当满足等式约束时返回该结点的数值。具体讲解见https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/kuai-man-zhi-zhen-by-powcai-4/。
代码:
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:def EntryNodeOfLoop(self, pHead):# write code hereif not pHead or not pHead.next :return None# 快慢指针 slow = pHeadfast = pHead# 重新开始 start = pHead while fast and fast.next: slow = slow.next fast = fast.next.next #找到相遇点#根据初始点到环入口的步数s等于相遇点到环入口的步数(r-m)加上环的长度r的整数倍进行while判断#即s=(n-1)r+(r-m)if slow == fast: while slow != start: slow = slow.next start = start.next return slow #返回的是该节点的数值
扩展题目:判断链表是否是环形链表
题目来自leetcode(https://leetcode-cn.com/problems/linked-list-cycle/)
题目描述:给定一个链表,判断链表中是否有环。为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
思路:和第8题类似,只是返回的是True或False。这里不需要满足第8题的约束条件,因为对于快慢指针而言,不是“环形链表”的话,就不会相遇。
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = Noneclass Solution(object):def hasCycle(self, head):""":type head: ListNode:rtype: bool"""if not head:return Falsewalker = headrunner = head.nexttry:while walker!=runner:walker = walker.nextrunner = runner.next.nextreturn Trueexcept:return False
二叉搜索树与双向链表
题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:先进行中序遍历,然后改变链表的指针指向。
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:def treeToList(self,root):if not root:return []return self.treeToList(root.left)+[root]+self.treeToList(root.right)def Convert(self, pRootOfTree):# write code herelist1=self.treeToList(pRootOfTree)if len(list1)==0:return Noneif len(list1)==1:return pRootOfTreefor i in range(len(list1)-1):list1[i].right=list1[i+1]list1[i+1].left=list1[i]return list1[0]
结尾
亲爱的读者朋友:感谢您在繁忙中驻足阅读本期内容!您的到来是对我们最大的支持❤️
正如古语所言:"当局者迷,旁观者清"。您独到的见解与客观评价,恰似一盏明灯💡,能帮助我们照亮内容盲区,让未来的创作更加贴近您的需求。
若此文给您带来启发或收获,不妨通过以下方式为彼此搭建一座桥梁: ✨ 点击右上角【点赞】图标,让好内容被更多人看见 ✨ 滑动屏幕【收藏】本篇,便于随时查阅回味 ✨ 在评论区留下您的真知灼见,让我们共同碰撞思维的火花
我始终秉持匠心精神,以键盘为犁铧深耕知识沃土💻,用每一次敲击传递专业价值,不断优化内容呈现形式,力求为您打造沉浸式的阅读盛宴📚。
有任何疑问或建议?评论区就是我们的连心桥!您的每一条留言我都将认真研读,并在24小时内回复解答📝。
愿我们携手同行,在知识的雨林中茁壮成长🌳,共享思想绽放的甘甜果实。下期相遇时,期待看到您智慧的评论与闪亮的点赞身影✨!
万分感谢🙏🙏您的点赞👍👍、收藏⭐🌟、评论💬🗯️、关注❤️💚~
自我介绍:一线互联网大厂资深算法研发(工作6年+),4年以上招聘面试官经验(一二面面试官,面试候选人400+),深谙岗位专业知识、技能雷达图,已累计辅导15+求职者顺利入职大中型互联网公司。熟练掌握大模型、NLP、搜索、推荐、数据挖掘算法和优化,提供面试辅导、专业知识入门到进阶辅导等定制化需求等服务,助力您顺利完成学习和求职之旅(有需要者可私信联系)
友友们,自己的知乎账号为“快乐星球”,定期更新技术文章,敬请关注!