剑指offer在线编程(08-14)【9】

 Date: 2019-08-14

1.  链表中环的入口结点   (考察知识点:链表)

题目描述

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

分析: 第一种不考虑时复杂度时,可以直接利用一个列表对环形链表进行遍历的元素存储,这样的时间复杂是O(N).在存储时,如果该元素已经在list中了,则表明到环形链表的入口了。否则继续向后遍历。

 

# -*- 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
        p = pHead
        reslist = []
        while p:
            if p in reslist:
                return p
            else:
                reslist.append(p)
            p = p.next

第二种思路来自剑指offer的思想:

如果链表中 有n个结点,指针P1在链表上向前移动n步,然后两个指针以相同的速度向前移动。当第二个指针指向环的入口结点时,第一个指针已经围绕着环走了一圈又回到了入口结点。但是首先要得到环中结点的数目。计算数目的思想是:上面相遇的节点一定是在环中,因此可以从这个节点出发,一边继续向前移动一边计数,下次再回到这个节点时,就可以得到环中的数目了。

具体:

  1. 第一步,找环中相汇点。分别用p1,p2指向链表头部,p1每次走一步,p2每次走二步,直到p1==p2找到在环中的相汇点。
  2. 第二步,找环的入口。接上步,当p1==p2时,p2所经过节点数为2x, p1所经过节点数为x, 设环中有n个节点, p2比p1多走一圈有2x=n+x; n=x; 可以看出p1实际走了一个环的步数,再让p2指向链表头部,p1位置不变,p1,p2每次走一步直到p1==p2; 此时p1指向环的入口。

 

# -*- 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 or not pHead.next.next:
            return None
        # 让慢指针每次走一步,快指针每次走两步,第一次相遇一定是在环中。相遇时:慢走x,则快走2x=x+n(块其实多走了一个环的数目),因此慢指针一共走了n步
        low = pHead.next
        fast = pHead.next.next
        while low != fast:
            if not low or not fast:
                return None
            low = low.next
            fast = fast.next.next
        # 相遇时,慢指针已经走了环数步,则让快指针从头开始,两指针每次都走一步,下次相遇必定在环的入口节点处。
        fast = pHead
        while low != fast:
            low = low.next
            fast = fast.next
        return fast

2. 删除链表中重复的结点   (考察知识点:链表)

题目描述

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

第一种思路:非递归的进行删除。

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

2.设置 pre ,nex指针, pre指针指向当前确定不重复的那个节点,而nex指针相当于工作指针,一直往后面搜索,,删除与当前pre重复的指针,最后在当前循环下记得删除pre指针!

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  # 将新节点设置为pre
        p = pHead       # 当前需要去判断是否重复的节点,需要更新
        nex = None       # 初始化下一个节点
        while p != None and p.next != None:  # 当p非空,p下一节点也非空是,设置nex,否则直接返回结束
            nex = p.next
            if p.val == nex.val:  # 这一个if语句,先固定p,然后判断nex和p的值,如果相等删除nex,然后不断往下寻找重复的nex,最后不满足跳出while语句进行更新
                while nex != None and nex.val == p.val:
                    nex = nex.next
                pre.next = nex
                p = nex
            else:  # 如果p和nex的值不等,pre和p向下遍历,进行判断
                pre = p
                p = p.next
        return new_head.next  # 因为new_head为新增头结点,所以返回的是其next节点

第二种思路:牛客上有人用的递归的思想:(时间复杂度要高一些,毕竟递归)

class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        # 先决条件
        if pHead is None or pHead.next is None:
            return pHead
        head1 = pHead.next
        if head1.val != pHead.val: # 如果两节点值不相等,则往下递归调用判断
            pHead.next = self.deleteDuplication(pHead.next)
        else:
            while pHead.val == head1.val and head1.next is not None: # 否则,如果相等且head1.next非空,删除head1,并while循环
                head1 = head1.next
            if head1.val != pHead.val:  # 删除节点终止后,节点不相等时,递归判断head1的后续节点情况
                pHead = self.deleteDuplication(head1)
            else:
                return None
        return pHead

3.  二叉树的下一个结点   (考察知识点:树)

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

  
思路:

(1) 若该节点存在右子树:则下一个节点为右子树最左子节点(如图节点 B )

(2) 若该节点不存在右子树:这时分两种情况:

        2.1 该节点为父节点的左子节点,则下一个节点为其父节点(如图节点 D )

        2.2 该节点为父节点的右子节点,则沿着父节点向上遍历,直到找到一个节点的父节点的左子节点为该节点,则该节点的父节点下一个节点(如图节点 I ,沿着父节点一直向上查找找到 B ( B 为其父节点的左子节点),则 B 的父节点 A 为下一个节点)。可以综合考虑第二种情况

# -*- coding:utf-8 -*-
# class TreeLinkNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
#         self.next = None
class Solution:
    def GetNext(self, pNode):
    # write code here
        if pNode.right: #有右子树
            p=pNode.right
            while p.left:
                p=p.left
            return p
        while pNode.next:   #无右子树,则找第一个当前节点是父节点左孩子的节点
             if(pNode.next.left==pNode):  # 如果第一次if就成立了,就表明其是父节点的左孩子,返回父节点即可
                return pNode.next
             pNode = pNode.next   #否则,沿着父节点向上遍历        
        return  None  #到了根节点仍没找到,则返回空

4.  对称的二叉树 (考察知识点:树)

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

分析:有很对不同的处理细节,有点直接采用递归的思想,有的是将树赋值一棵,然后左右子树进行相等的遍历。

我在这里采用 的是:直接往下走,进行逐步比较。首先需要判断四种情况:

树根为空,True;

左子树空,右子树不为空,False

左子树不为空,右子树空,False

左右子树均为空,True

最后递归遍历左右子树均不为空的情况,进行值的比较!

# -*- coding:utf-8 -*-  我的代码,但没有通过,思路和后面一版本的代码相同,疑惑。难道是子函数的使用?
# class TreeNode: 
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def isSymmetrical(self, pRoot):
        # write code here
        if not pRoot:
            return True
        if not pRoot.left and not pRoot.right:
            return True
        if pRoot.left and not pRoot.right:
            return False
        if not pRoot.left and pRoot.right:
            return False
        while pRoot.left and pRoot.right:
            if pRoot.left.val == pRoot.right.val:
                return self.isSymmetrical(pRoot.left) and self.isSymmetrical(pRoot.right) 
            return False
        return True


# -*- coding:utf-8 -*-  # 通过的代码
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def isSymmetrical(self, pRoot):
        # write code here
        def is_same(p1,p2):
            if not p1 and not p2:    # 左右子树均为空,True
                return True
            if (p1 and p2) and p1.val==p2.val:   # 左右子树均不为空,且值相等时
                return is_same(p1.left,p2.right) and is_same(p1.right,p2.left)
            return False
        if not pRoot:  # 树根为空,True
            return True
        if pRoot.left and not pRoot.right:  # 左子树不空,右子树为空,False
            return False
        if not pRoot.left and pRoot.right:   # 右子树不空,左子树为空,False
            return False
        return is_same(pRoot.left,pRoot.right) # 否则递归调用

5.  按之字形顺序打印二叉树   (考察知识点:栈+树)

题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

分析:

* 大家的实现很多都是将每层的数据存进ArrayList中,偶数层时进行reverse操作。但是在海量数据时,这样效率太低了。

 如果面试,算法考的就是之字形打印二叉树,用reverse,会被鄙视,面试官会说海量数据时效率根本就不行。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Print(self, pRoot):
        # write code here
        root=pRoot
        if not root:
            return []
        level=[root]
        result=[]
        lefttoright=False  # 标记符号,判定是从左往右False,还是从右往左True
        while level:
            curvalues=[] # 目前的值,一般是从左往右
            nextlevel=[]  # 该层的节点记录,左节点+右节点
            for i in level:
                curvalues.append(i.val)
                if i.left:
                    nextlevel.append(i.left)
                if i.right:
                    nextlevel.append(i.right)
            if lefttoright:   
                curvalues.reverse() # 第一层是从左往右False,下一层时从右往左True,交替
            if curvalues:
                result.append(curvalues)  # 将正确的顺序后的该层值进行放入最后的result中
            level=nextlevel #  循环中使用的level的更新
            lefttoright=not lefttoright  # 标记的变化
        return result

其他方法: 没有利用reverse,进行先存后改变顺序,而是一步到位:

'''解法:利用一个标志变量flag来标记从左往右还是从右往走
如果从左往右,那就从头到尾遍历当前层的节点current_nodes,然后将左孩子和右孩子分别append到一个list new_nodes中
如果从右往前,那就从尾到头遍历当前层的节点current_nodes,然后将右孩子和左孩子分别insert到一个list new_nodes中
这样得到的new_nodes还是从左到右有序的'''
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:   
    def Print(self, pRoot):
        # write code here
        if pRoot == None:
            return []
        falg = 0  # 0表示从左往右,1表示从右往左
        node_list = [[pRoot]]
        result = []
        while node_list:
            current_nodes = node_list[0] # 当前层的节点            
            node_list = node_list[1:]
            new_nodes = [] # 下一层的节点,按照从左往右的顺序存储            
            res = [] # 当前层得到的输出
            while len(current_nodes) > 0:
                # 从左往右
                if falg == 0:
                    res.append(current_nodes[0].val)
                    if current_nodes[0].left != None:
                        new_nodes.append(current_nodes[0].left)
                    if current_nodes[0].right != None:
                        new_nodes.append(current_nodes[0].right)
                    current_nodes = current_nodes[1:]
                # 从右往左
                else:
                    res.append(current_nodes[-1].val)
                    if current_nodes[-1].right != None:
                        new_nodes.insert(0, current_nodes[-1].right)
                    if current_nodes[-1].left != None:
                        new_nodes.insert(0, current_nodes[-1].left)
                    current_nodes = current_nodes[:-1]
            result.append(res)
            falg = 1 - falg
            if new_nodes:
                node_list.append(new_nodes)
        return result

6. 把二叉树打印成多行   (考察知识点:树+队列)

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

分析:此题比上题还要简单些,第一种实现如下:直接从上到下分别存储从左到右的节点值和从左到右每个节点的左右孩子的节点。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:   
    # 返回二维列表[[1,2],[4,5]]
    def Print(self, pRoot):
        # write code here
        if pRoot == None:
            return []
        node_list = [[pRoot]]
        result = [] # 每一层的节点的值从左到右写成一个list
        while node_list:
            current_nodes = node_list[0] # 每次取第一个值为当层从左到右的节点顺序,当前层的节点            
            node_list = node_list[1:]   # 取完之后,node_list会进行第一个值的删除更新
            new_nodes = [] # 下一层的节点,按照从左往右的顺序存储            
            res = [] # 当前层节点的值按照从左到右的顺序输出
            while len(current_nodes) > 0:
                res.append(current_nodes[0].val)  # 进行一个节点的值的存储res中
                if current_nodes[0].left != None:  #节点的左右孩子节点按照顺序存储current_nodes中
                    new_nodes.append(current_nodes[0].left)
                if current_nodes[0].right != None:
                    new_nodes.append(current_nodes[0].right)
                current_nodes = current_nodes[1:]  # 当前结点的删除更新
            result.append(res)
            if new_nodes:
                node_list.append(new_nodes)
        return result

一种更简洁的实现方式:初始化两个list,res用作存放每一行节点值row的最大list。tmp用于存放不断更新的当前节点。

1. 首先进行每一行节点值row的更新和存储,并将其保存到 res中

2. 然后进行当层节点tmp的更新:不断删除该层的节点(从左到右),同时获取其左右孩子的节点到tmp中。[注意:这里只需要进行该层节点数len(tmp)次循环:删除和更新]

# -*- coding:utf-8 -*-  # 个人支持这种,简单易懂
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:   
    # 返回二维列表[[1,2],[4,5]]
    def Print(self, pRoot):
        # write code here
        if not pRoot: # 根节点为空,则最后结果也为空
            return []
        res = []  # 用于存放每一层节点值row的大list
        tmp = [pRoot]  # 用于存放当层的节点,从左到右的顺序
        while tmp:
            row = [] # 当层节点值的存储,形成小list
            for i in tmp:
                row.append(i.val)
            res.append(row)  # 存储完该层的节点值后,对存储该层节点tmp进行删除更新。
            for i in range(len(tmp)):
                t = tmp.pop(0)  # 从左到右删除节点,同时存储该节点的左右孩子节点
                if t.left:
                    tmp.append(t.left)
                if t.right:
                    tmp.append(t.right)
        return res
                    

7.  序列化二叉树   (考查知识点:队列 + 树)  (Diff  多看!!!)

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。


二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

分析:首先解析题目:序列化实质上就是按照某一种遍历的方式(这里采用先序遍历的方式),不断遍历树的值得到一个字符串,注意空节点#和','进行分割!;反序列化实质上就是二叉树的重构,之前有一题是二叉树的重构,在哪里我们用到了先序遍历和中序遍历进行树的重建,但是这里我们直接采用先序遍历的字符串结果就可以进行树的重建。

1) 序列化:初始化一个空列表,首先判断函数运行的先决条件;如果不为空,先增添当前节点(父节点)的值(注意转成str形式),然后递归调用该父节点的左孩子,如果存在则进行值的增添(在调用过程中,可能会调用的左子树的左孩子及右孩子);然后再递归调用该父节点的右子树进行元素的增添。

2)反序列化:会在类下先初始化一个全局成员变量flag来实现记录目前应该获取s中的那一个元素值,每次递归调用一次反序列化函数,flag+1,根节点是0,知道len(s)-1 结束。如果超出了len(s)的长度,则表明序列中的值已经重建二叉树完成;否则当前值非#时,先在root上增加节点,然后序列化结果中递归增加左节点,然后再是右节点!

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def __init__(self):
        self.flag = -1
        
    def Serialize(self, root):
        # write code here
        res = []
        if not root:  # 如果当前递归调用的根节点为空,则返回#
            return '#'
        res.append(str(root.val))  # 否则非空时,先增加根节点的值
        l = self.Serialize(root.left)   # 递归调用当前节点的左子树,会一直往左下调用直到到达叶节点,才往回增加元素
        res.append(l)
        r = self.Serialize(root.right)   # 递归调用当前节点的右子树,如果右子树有左节点,先递归左节点,然后右节点。
        res.append(r)
        return ','.join(res)

    def Deserialize(self, s):
        # write code here
        self.flag += 1  # 用于记录每次取s中哪一点,每递归调用一次,+1,从根节点为0到len(s)-1
        l = s.split(',')  # 将s进行分割
        if (self.flag >= len(s)):  #  如果当前累计的Flag已经到达了最大深度,则直接返回,不再继续增加节点
            return None
        root = None
        if (l[self.flag] != '#'):  # 判断l中当前标记的元素是否为#,如果不是增加节点,然后先不断往左边增加节点值
            root = TreeNode(int(l[self.flag]))
            root.left = self.Deserialize(s)  # 当左边不到增加节点,直到第一次遇到#,则当前循环不增加节点,表明左边已经到底
            root.right = self.Deserialize(s)  # 从而开始往右边增加节点(注意在往左边递归的时候,会遇到左节点有右孩子的情况,仍然是左边走完,再回到右边)
        return root
# 更简洁的序列化版本,一样的递归调用思想
"""
 def Serialize(self, root):
        # write code here
        if not root:
            return '#,'
        return str(root.val)+','+self.Serialize(root.left)+self.Serialize(root.right)
"""

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章