《剑指offer》-数据结构篇-链表

题目

  1. 从尾到头打印链表

  2. 反转链表

  3. 链表中倒数第K个结点

  4. 合并两个/K个有序链表

  5. 复杂链表的复制

  6. 删除链表中的重复元素

  7. 两个链表的第一个公共结点

  8. 链表中环的入口结点

  9. 二叉搜索树与双向链表

代码实现

从尾到头打印链表

题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

思路:

  • 方式1:先加载链表中的所有值到新建的列表中,然后将该列表反转,返回一个ArrayList。

  • 方式2:借助辅助栈,一个栈存储链表中由前至后结点值,另一个栈存储该栈依次弹出结点值。

python 复制代码
# -*- 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:先加载链表中的所有值到列表中,然后将该列表反转,返回一个ArrayList
        ret=[]
        head=listNode
        while(head):
            ret.append(head.val)
            head=head.next
        ret.reverse()
        return ret
        """
        #解法2:利用辅助栈来解决
        stack = []
        result_array = []
        node_p = listNode
        while 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,出现错误。反转链表很典型,一些链表的复杂题目是在其基础上产生的。

python 复制代码
# -*- 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
        #判断特殊情况
        #对于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 = pre
        return pHead

链表中倒数第K个结点

题目描述:输入一个链表,输出该链表中倒数第k个结点。

思路:采用快慢指针的方式。让一个指针先走k步,当该指针到达None时,另一个指针指向该链表中的倒数第k个结点,返回该指针所对应的结点值。快慢指针的方式的题目比较重要,很多链表题目都是使用这种方法求解的。

python 复制代码
# -*- 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 = head
        slow_p = head 
        #确保可以得到与最初的fast_p位置相隔k个结点的新的新的fast_p位置
        #该位置也是和slow_p相距k个结点的位置
        for _ in range(k):
            if fast_p:
                fast_p = fast_p.next
            else:
                return None
        #由于快慢指针相距为k,设原链表的长度为n,则最终慢指针的位置为n-k+1
        #即倒数第k个结点的位置
        while fast_p:
            fast_p = fast_p.next
            slow_p = slow_p.next
        return slow_p #使用slow_p返回该节点的数值

合并两个/K个有序链表

题目一:合并两个有序链表

题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

思路:构建新的列表存储两个单调递增的链表的数值,对列表进行排序生成新的列表后,再由其新的生成链表。

python 复制代码
# -*- 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
        p1 = pHead1
        p2 = pHead2
        #新建一个链表
        l = list()  #替换为[]会导致执行的速度下降
        #加载链表的值到列表
        while p1 or p2:
            if p1:
                l.append(p1.val)
                p1 = p1.next
            if p2:
                l.append(p2.val)
                p2 = p2.next
        l.sort()
        #构建新的链表
        for i in range(len(l)):
            if i ==0:
                l3 = ListNode(l[i])
                p3 = l3
            else:
                p3.next = ListNode(l[i])
                p3 = p3.next
        #注意返回的是链表,而不是最后一个结点,因而用了l3
        return l3 if l else None 

题目二:合并K个有序链表

题目:leetcode链接(https://leetcode-cn.com/problems/merge-k-sorted-lists/)

思路:分治递归,详见:合并K个有序链表

N是所有链表中元素的总数,K是链表数量,时间复杂度为NlogK,空间复杂度为logK

python 复制代码
# 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) // 2
            return 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 None
        if len(lists)==1: return lists[0]
        head1, head2 = lists
        head = dump = ListNode(0)
        while head1 and head2:
            if head1.val < head2.val:
                head.next = head1
                head1 = head1.next
            else:
                head.next = head2
                head2 = head2.next
            head = head.next
        head.next = head1 if head1 else head2
        return dump.next

复杂链表的复制

题目描述:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路:先复制按照指向正常结点的结点所构成的链表,再在基础上复制指向任意结点的结点所构成的链表,最终输出复杂链表。

python 复制代码
# -*- 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):
        # write code here
        #判断链表不为空
        if not pHead: return None
        p = pHead
        new_h = RandomListNode(p.label)
        pre_p = new_h
        random_map = {pHead: new_h}
        p = p.next
        #先完成对于链表的复制
        while p:
            new_p = RandomListNode(p.label)
            random_map[p] = new_p
            pre_p.next = new_p
            pre_p = pre_p.next
            p = p.next
        p = pHead
        new_p = new_h
        #然后完成对于自由结点的复制
        while p:
            random_p = p.random
            if random_p:
                new_p.random = random_map[random_p]
            p = p.next
            new_p = new_p.next
        #返回复杂链表
        return new_h 

删除链表中的重复元素

题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路:依次遍历链表中的数值,将不重复结点的指针指向下一个不重复的结点,最后记得输出整个链表而非得到的链表中的最后一个结点的数值。题目很典型,类似的题目是:对于链表中的重复结点,只保留一次数值。

python 复制代码
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        dummy_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题类似,只是指针指向的不同。

python 复制代码
# 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 = head
        while cur:
            while cur.next and cur.val == cur.next.val:
                cur.next = cur.next.next
            cur = cur.next
        #注意返回head
        return head

两个链表的第一个公共结点

题目描述:输入两个链表,找出它们的第一个公共结点。

思路:

方法1:两个指针循环遍历链表,指针指向的数值相同时,说明是第一个公共结点。

方法2:找到两个链表的长度差k。让一个指针指向较长的链表的第k+1个结点,一个指针指向较短的链表的第一个结点,当指针指向的数值相同时,说明是第一个公共结点。

注意:题目描述中的两个链表A和B自公共结点L之后的结点都是一样的。

python 复制代码
# -*- 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 None
        pa = pHead1
        pb = pHead2        
        #对于两个链表,相交的第一个节点之后的部分也是相同的
        while pa != pb:
            if pa:
                pa = pa.next
            else:
                pa = pHead2
            if pb:
                pb = pb.next
            else:
                pb = pHead1
        #pa==pb对应的是相交的节点。注意这里是返回节点,因而也可以是返回pa。
        return pb
        """
        #解决方法2:采用对其最后的结点对齐的方式,使得较长的链表的
        #头结点先领先两者的长度差大小的步数,之后指针指向相同的结点值时就找到了第一个公共结点
        p1 = pHead1
        p2 = pHead2
        n_p1 = 0
        n_p2 = 0
        while p1:
            p1 = p1.next
            n_p1 += 1
        while p2:
            p2 = p2.next
            n_p2 += 1
        if n_p1 < n_p2:
            pHead1, pHead2 = pHead2, pHead1
        for _ in range(n_p1 - n_p2):
            pHead1 = pHead1.next
        while pHead1:
            if pHead1 == pHead2:
                return pHead1
            else:
                pHead1 = pHead1.next
                pHead2 = pHead2.next
        return None

链表中环的入口结点

题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路:利用快慢指针。刚开始指针都指向链表的第一个结点,先找到两个指针相遇的结点值。之后,根据链表的第一个结点到环入口的步数s等于相遇点到环入口的步数(r-m)加上环的长度r的整数倍进行while判断,当满足等式约束时返回该结点的数值。具体讲解见https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/kuai-man-zhi-zhen-by-powcai-4/。

python 复制代码
代码:
# -*- 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 or not pHead.next :
                return None
        # 快慢指针 
        slow = pHead
        fast = 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题的约束条件,因为对于快慢指针而言,不是"环形链表"的话,就不会相遇。

python 复制代码
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None
 
class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        if not head:
            return False
        walker = head
        runner = head.next
        try:
            while walker!=runner:
                walker = walker.next
                runner = runner.next.next
            return True
        except:
            return False

二叉搜索树与双向链表

题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

思路:先进行中序遍历,然后改变链表的指针指向。

python 复制代码
# -*- 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 here
        list1=self.treeToList(pRootOfTree)
        if len(list1)==0:
            return None
        if len(list1)==1:
            return pRootOfTree
        for 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、搜索、推荐、数据挖掘算法和优化,提供面试辅导、专业知识入门到进阶辅导等定制化需求等服务,助力您顺利完成学习和求职之旅(有需要者可私信联系)

友友们,自己的知乎账号为**"快乐星球"**,定期更新技术文章,敬请关注!