剑指offer(python)--链表

01-从尾到头打印链表

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

问题:1 ,如何输入一个链表?—字典
2. 如何获得链表值 --字典映射 ?指针 数值
3. 如何从头到尾顺序返回?---- for i in range ()返回 总体来说就是将字典转化为列表 (简单说就是把这个链表上的节点一个个读到列表中,然后用[::-1]切片的方法实现逆序)
b = a[i:j] 表示复制a[i]到a[j-1],以生成新的list对象

补充
> a = [0,1,2,3,4,5,6,7,8,9]
b = a[i:j] # [1,2]
当i缺省时,默认为0,即 a[:3]相当于a[0:3] ;
当j缺省时,默认为len(alist), 即a[1:]相当于a[1:10]
当i,j都存在时,表示复制i到j的数据
当i,j都缺省时,a[:]就相当于完整复制一份a

b = a[i:j:s]表示:i,j与上面的一样,但s表示步进,
缺省为1. 所以a[i:j:1]相当于a[i:j]
当s<0时,i缺省时,默认为-1.
j缺省时,默认为-len(a)-1 所以a[::-1]相当于 a[-1:-len(a)-1:-1],
也就是从最后一个元素到第一个元素复制一遍,即倒序。
http://www.cnblogs.com/mxh1099/p/5804064.html

# 实现一个链表类,只有一个值val和一个指向下一个节点的next'指针'
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    # 返回从尾部到头部的列表值序列,例如[1,2,3]
    def printListFromTailToHead(self, listNode):
        # @listNode: 头结点
        # write code here
        l = []
        while listNode:
            l.append(listNode.val)
            listNode = listNode.next
        return l[::-1] 
# 实例化
# 创建链表 a->b->c
a = ListNode(1)
b = ListNode(2)
c = ListNode(3)
a.next = b
b.next = c

if __name__=='__main__':
    demo = Solution()
    print(demo.printListFromTailToHead(a))

02–链表中倒数第K个结点**

输入一个链表,输出该链表中倒数第k个结点。
思路: 先计算出所有的节点n,倒数第k个节点即是从前往后第n-k+1个节点

class Solution:
    def FindKthToTail(self, head, k):
        if  k<=0 or head==None:
            return None
        count=0
        p=head
        while p!=None:
            count+=1 # 算出链表节点数
            p=p.next
        if k>count:
            return None
        number=count-k+1  #需要走的步数
        cnt=0
        p=head
        while p!=None:
            cnt=cnt+1
            if cnt==number:
                return p
            p=p.next

思路2为了能够只遍历一次就能找到倒数第k个节点,可以定义两个指针:

(1)第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动;

(2)从第k步开始,第二个指针也开始从链表的头指针开始遍历;

(3)由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def FindKthToTail(self, head, k):
        if k<=0 or head==None:
            return None
        else:
            count=0
            p=head
            mark=False
            ans=head #第二个指针
            while p!=None:
                count=count+1
                if count>k:
                    ans=ans.next
                p=p.next
            if count<k:
                ans=None
            return ans

03–反转链表

输入一个链表,反转链表后,输出新链表的表头。
思考:反转后新链表的表头是不是是原始链表的最后一个,先给定一个空的链表newList,然后判断传入的链表head是不是空链表或者链表元素只有一个,如果是,直接返回就可以。如果不是,则对链表进行迭代,然后给一个临时变量temp存储head.next,然后改变head.next的指向newList,然后把head赋值给newList,接着让head等于临时变量temp,就这样一直迭代完整个链表,返回newList就可以
在这里插入图片描述

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    # 返回ListNode
    def ReverseList(self, head):
        if not head or not head.next:
            return head
        last=None
        while head:
            tmp=head.next
            head.next=last
            last=head
            head=tmp
        return last

04–合并两个排序链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路> 同步遍历,先找到链表中头结点比较小的作为头结点 每一次遍历要比较节点大小
还要判断两个节点长短不一致,

在这里插入图片描述

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    # 返回合并后列表
    def Merge(self, pHead1, pHead2):
        dummy=ListNode(0)
        pHead=dummy
        while pHead1 and pHead2:
            if pHead1.val>=pHead2.val:
                dummy.next=pHead2
                pHead2=pHead2.next
            else:
                dummy.next=pHead1
                pHead1=pHead1.next
            dummy=dummy.next
        if pHead1:
            dummy.next=pHead1
        else:
            dummy.next=pHead2
        return pHead.next
        # write code here

05-复杂链表的复制

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

1.如果链表为空链表,则返回本身即可
2.如果非空 需要进行复制操作,如果没有特殊指针,只需要复制next我相信大家都能很快做出来,但是加上特殊指针这就需要一定技巧,因为特殊指针随便指,而你每次找特殊指针所指的节点都需要从头开始遍历找起,这显然复杂度高达O(n²)

方法1:

在不使用辅助空间的情况下实现O(N)的时间效率。

  1. 把复制的结点链接在原始链表的每一对应结点后面

  2. 把复制的结点的random指针指向被复制结点的random指针的下一个结点

  3. 拆分成两个链表,奇数位置为原链表,偶数位置为复制链表,注意复制链表的最后一个结点的next指针不能跟原链表指向同一个空结点None,next指针要重新赋值None(判定程序会认定你没有完成复制)

原文:https://blog.csdn.net/jiangjiang_jian/article/details/81490693
https://blog.csdn.net/qq_33431368/article/details/79296360

如图
首先第一步 复制原来的链表,顺次连接形成新链表

在这里插入图片描述

 cloNode = pHead
        while cloNode:
            #完成第一步的核心操作
            node = RandomListNode(cloNode.label)
            node.next = cloNode.next
            cloNode.next = node
              cloNode = node.next #下一次操作

第二步,利用原节点的random指向,来用复制的相应节点的random

  cloNode = pHead
        while cloNode:
            node = cloNode.next #指向复制的结点
            if cloNode.random: #如果原节点有特殊指针
                node.random = cloNode.random.next #则复制的节点的特殊指针指向原节点的特殊指针指向的下一个值  看图更好理解一些
            cloNode = node.next

最后一步,将复制好的链表拆分出来,或者说将 偶数位的节点重新拆分合成新的链表,得到的就是复制的链表

在这里插入图片描述

 cloNode = pHead
        pHead = pHead.next
        while cloNode.next:
            #完成第三步的核心操作 此时节点指向隔了一个节点的节点
            node = cloNode.next
            cloNode.next = node.next
            
            cloNode = node #下一个节点的操作

这个操作其实就是将两个链表顺次全都拆分出来,一个很关键的步骤 pHead = pHead.next 如果没有这句话,最后得到的pHead就是原链表的开头了。

总程序如下:

    # -*- 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 pHead
            cloNode = pHead
            while cloNode:
                node = RandomListNode(cloNode.label)
                node.next = cloNode.next
                cloNode.next = node
                cloNode = node.next
            cloNode = pHead
            while cloNode:
                node = cloNode.next
                if cloNode.random:
                    node.random = cloNode.random.next
                cloNode = node.next
            cloNode = pHead
            pHead = pHead.next
            while cloNode.next:
                node = cloNode.next
                cloNode.next = node.next
                cloNode = node
            return pHead

方法2:递归

class Solution:
    def Clone(self, head):
        if not head: return
        newNode = RandomListNode(head.label)
        newNode.random = head.random
        newNode.next = self.Clone(head.next)
        return newNode

方法3:

实际上我们可以通过空间换取时间,将原始链表和复制链表的结点通过哈希表对应起来,这样查找的时间就从O(N)变为O(1)。具体如下:

复制原始链表上的每个结点N创建N’,然后把这些创建出来的结点用pNext连接起来。同时把<N,N’>的配对信息方法一个哈希表中;然后设置复制链表中的每个结点的pSibling指针,如果原始链表中结点N的pSibling指向结点S,那么在复制链表中,对应的N’应该指向S’。

时间复杂度:O(N)

class Solution:
    def Clone(self, head):
        nodeList = []     #存放各个节点
        randomList = []   #存放各个节点指向的random节点。没有则为None
        labelList = []    #存放各个节点的值
 
        while head:
            randomList.append(head.random)
            nodeList.append(head)
            labelList.append(head.label)
            head = head.next
        #random节点的索引,如果没有则为1   
        labelIndexList = map(lambda c: nodeList.index(c) if c else -1, randomList)
 
        dummy = RandomListNode(0)
        pre = dummy
        #节点列表,只要把这些节点的random设置好,顺序串起来就ok了。
        nodeList=map(lambda c:RandomListNode(c),labelList)
        #把每个节点的random绑定好,根据对应的index来绑定
        for i in range(len(nodeList)):
            if labelIndexList[i]!=-1:
                nodeList[i].random=nodeList[labelIndexList[i]]
        for i in nodeList:
            pre.next=i
            pre=pre.next
        return dummy.next

06–二叉搜索树与双向链表

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

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

题目分析: 二叉搜索树 根节点大于左节点, 根节点小于右节点
比如将二元查找树


转换成双向链表(双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。)下面是数组,链表的一些定义操作
https://www.cnblogs.com/skywang12345/p/3561803.html

在这里插入图片描述
思路:
1.二叉树中序遍历的结果与链表的顺序一致,所以可以采用中序遍历的方法来修改二叉树的指针

2.该题的关键是,如何将左子树的最大值与右子树的最小值通过根root连接起来,比如题目的8和12,这也是细节部分

3.写递归程序最重要的是弄明白递归进入的条件、递归返回的状态,如果递归进入时改变了环境,返回时应当恢复环境,就像栈的操作一样

4.使用指针变量时,要记得初始化

5.该算法没有返回链表头,而是返回了root。
在这里插入图片描述

用中序遍历和递归,且以这张图为第一步,后续的左右子树均按照图示的思路来做,并且前后不用任何中间节点。但慢慢发现,如果用递归,在不新建任何中间节点的情况下,我只能实现到:46810161412,并且图示提供的思路也不够精确。
遂求助网络,但得到的解法均需要新建辅助节点,且绝大多数代码不够简练。在牛客网该题下大家的讨论中倒是有不错的思路,只是没有配文的情况下还需要点时间理解。鉴于自己没查到该题精简且详细的解题思路,我就露个拙,实现Python解法,并配上我的理解。

思路

核心算法依旧是中序遍历 不是从根节点开始,而是从中序遍历得到的第一个节点开始
定义两个辅助节点listHead(链表头节点)、listTail(链表尾节点)。事实上,二叉树只是换了种形式的链表;listHead用于记录链表的头节点,用于最后算法的返回;listTail用于定位当前需要更改指向的节点。了解了listHead和listTail的作用,代码理解起来至少顺畅80%。
提供我画的算法的过程图,有点丑,但有助于理解(帮你们画了,你们就不用画啦),另外图中右上角步骤三应该是“2”标红,“2”和“1”中间的连接为单线黑~~~

在这里插入图片描述
https://blog.csdn.net/jiangjiang_jian/article/details/81637574

class Solution:
    def __init__(self):
        self.listHead = None
        self.listTail = None
    def Convert(self, pRootOfTree):
        if pRootOfTree==None:
            return
        self.Convert(pRootOfTree.left)
        if self.listHead==None:
            self.listHead = pRootOfTree
            self.listTail = pRootOfTree
        else:
            self.listTail.right = pRootOfTree
            pRootOfTree.left = self.listTail
            self.listTail = pRootOfTree
        self.Convert(pRootOfTree.right)
        return self.listHead

在这里插入图片描述在这里插入图片描述

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Convert(self, pRootOfTree):
        if not pRootOfTree:
            return None
         
        p = pRootOfTree
         
        stack = []
        resStack = []
         
        while p or stack:
            if p:
                stack.append(p)
                p = p.left
            else:
                node = stack.pop()
                resStack.append(node)
                p = node.right
             
        resP = resStack[0]
        while resStack:
            top = resStack.pop(0)
            if resStack:
                top.right = resStack[0]
                resStack[0].left = top
         
        return resP
递归版本

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Convert(self, root):
        if not root:
            return None
        if not root.left and not root.right:
            return root
         
        # 将左子树构建成双链表,返回链表头
        left = self.Convert(root.left)
        p = left
         
        # 定位至左子树的最右的一个结点
        while left and p.right:
            p = p.right
         
        # 如果左子树不为空,将当前root加到左子树链表
        if left:
            p.right = root
            root.left = p
         
        # 将右子树构造成双链表,返回链表头
        right = self.Convert(root.right)
        # 如果右子树不为空,将该链表追加到root结点之后
        if right:
            right.left = root
            root.right = right
             
        return left if left else root

07–两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共结点。
次将链表中的元素压入两个栈中,然后每次从两个栈中抛出一个元素,直到抛出的结点相同时返回
后面的元素都是公共的

class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        # write code here
        lst1 = []
        lst2 = []
        result = []
 
        if not pHead1 or not pHead2:
            return None
 
        p1 = pHead1
        p2 = pHead2
 
        while p1:
            lst1.append(p1)
            p1 = p1.next
        while p2:
            lst2.append(p2)
            p2 = p2.next
 
        while lst1 and lst2:
            node1 = lst1.pop()
            node2 = lst2.pop()
            if node1 == node2:
                result.append(node1)
         
        if result:
            node = result.pop()
            return node

思路2:
直接把第一个链表丢到set里面,然后遍历第二个链表,找到第一个一样的节点,时间O(M+N)

  # -*- coding:utf-8 -*-
    # class ListNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.next = None
    class Solution:
        def FindFirstCommonNode(self, pHead1, pHead2):
            result_set = set()
            while pHead1:
                result_set.add(pHead1)
                pHead1 = pHead1.next
            while pHead2:
                if pHead2 in result_set:
                    return pHead2
                pHead2 = pHead2.next

09–删除链表中重复的节点

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

首先添加一个头节点,以方便碰到第一个,第二个节点就相同的情况

2.设置 pre ,last 指针, pre指针指向当前确定不重复的那个节点,而last指针相当于工作指针,一直往后面搜索。使用3个指针,一个指向前一个节点last,一个指向当前节点p,一个指向下一个节点p->next,当当前节点跟后一个节点相等时,不断往后遍历,找到第一个不等于当前节点的节点;然后用last
指向它;当当前节点跟后一个不相等时,将last 后移指向p,p后移一位

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        if pHead == None or pHead.next == None:
            return pHead
        new_head = ListNode(-1)
        new_head.next = pHead# # 因为需要两个指针,一个指着重复结点上一个结点,一个指着重复结点后一个值。
        pre = new_head
        p = pHead
        nex = None
        while p != None and p.next != None:
            nex = p.next
            if p.val == nex.val:
                while nex != None and nex.val == p.val:
                    nex = nex.next
                pre.next = nex
                p = nex
            else:
                pre = p
                p = p.next
        return new_head.next

09-反转链表

题目描述
输入一个链表,反转链表后,输出新链表的表头。
链表是通过一个个节点组成的,每个节点都包含了称为cargo的基本单元,它也是一种递归的数据结构。它能保持数据之间的逻辑顺序,但存储空间不必按照顺序存储。
如图:

在这里插入图片描述

链表的基本元素有:

节点:每个节点有两个部分,左边部分称为值域,用来存放用户数据;右边部分称为指针域,用来存放指向下一个元素的指针。
head:head节点永远指向第一个节点 tail: tail永远指向最后一个节点 None:链表中最后一个节点的指针域为None值

思路1:
因为链表是有head和tail,而他们是有一个方向的,因此我们想直接进行类似list[::-1]这种反转就不是太方便。在下面的算法中,我们通过将链表截断,而后再拼接的方式进行反转。

以{1,2,3}链表作为例子,来说明下面算法中while迭代的流程(手工debug…)

1.将{2,3}的地址指向给tmp
2.将last=None指向pHead.next,这个时候pHead链表就被截断了,pHead只剩下了1,因为他的下一步指向了None
3.将此时的pHead,也就是1指向给last,这时候last为{1}
4.将tmp={2,3}指向给pHead,此时这轮迭代结束,开启下一轮. 这时,pHead是{2,3},last是{1}

5.将{3}指向给tmp
6.将{1}指向给pHead.next,也就是2的下一步值,因此这时候pHead就变成了{2,1}
7.将{2,1}指向给last
8.将{3}指向给pHead,此时这轮迭代结束,开启下一轮. 这时,pHead是{3},last是{2,1}

9.将None指向了tmp
10.将{2,1}指向给pHead.next,也就是{3}的下一步值,这个时候pHead就变成了{3,2,1}
11.将{3,2,1}指向给last
12.将None指向给pHead,此时这轮迭代结束,while迭代结束。 这时pHead是{None},last是{3,2,1}
https://www.jianshu.com/p/e385d9c06672
根据下图,先给定一个空的链表newList,然后判断传入的链表head是不是空链表或者链表元素只有一个,如果是,直接返回就可以。如果不是,则对链表进行迭代,然后给一个临时变量temp存储head.next,然后改变head.next的指向newList,然后把head赋值给newList,接着让head等于临时变量temp,就这样一直迭代完整个链表,返回newList就可以
在这里插入图片描述

class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        if not pHead or not pHead.next:
            return pHead
        last =None
        while pHead:
            tmp = pHead.next#将下一步的地址指向给tmp
            pHead.next=last#将一个新的链表指向给旧链表pHead,这个时候就把pHead截断了,只剩下前面的链表值
            last=pHead#将旧链表的地址指向给新链表
            pHead=tmp#将旧链表原来的下一步只指向给pHead
        return last


思路2:–递归

思路:假设链表为[1,2,3,4,5]先迭代到链表末尾5,然后从5开始依次反转整个链表
如下图所示,先迭代待最后一位5,并且设置一个新的节点newList作为反转后链表的头结点,由于整个链表反转后的头就是最后一个数,所以newList存放的一直是反转后的头结点的地址,将head指向的地址赋值给head->next->next指针,并且一定要记得让head->next
=NULL,也就是断开现在指针的链接,否则新的链表形成了环,下一层head->next->next赋值的时候会覆盖后续的值。依次反转。
在这里插入图片描述

class Solution:
    # 返回ListNode
    def ReverseList(self, head):
        if head==None or head.next==None:
            return head
        newlist=ReverseList(head.next)
        head.next.next=head
        head.next=None
        return newlist
 

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

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

题目分析:
链表两个长度十分相等

链接:https://www.nowcoder.com/questionTerminal/6ab1d9a29e88450685099d45c9e31e46
来源:牛客网

依次将链表中的元素压入两个栈中,然后每次从两个栈中抛出一个元素,直到抛出的结点相同时返回
后面的元素都是公共的
class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        # write code here
        lst1 = []
        lst2 = []
        result = []
 
        if not pHead1 or not pHead2:
            return None
 
        p1 = pHead1
        p2 = pHead2
 
        while p1:
            lst1.append(p1)
            p1 = p1.next
        while p2:
            lst2.append(p2)
            p2 = p2.next
 
        while lst1 and lst2:
            node1 = lst1.pop()
            node2 = lst2.pop()
            if node1 == node2:
                result.append(node1)
         
        if result:
            node = result.pop()
            return node

思路2:
直接把第一个链表丢到set里面,然后遍历第二个链表,找到第一个一样的节点,时间O(M+N)

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        result_set = set()
        while pHead1:
            result_set.add(pHead1)
            pHead1 = pHead1.next
        while pHead2:
            if pHead2 in result_set:
                return pHead2
            pHead2 = pHead2.next
发布了58 篇原创文章 · 获赞 10 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_24429333/article/details/104535152