剑指offer-二叉树(python)

  • 二叉树部分
    • 0. 构建二叉树
    • 1. 重建二叉树
    • 2. 树的子结构
    • 3. 二叉树的镜像
    • 4. 从上往下打印二叉树
    • 5. 二叉搜索树的后序遍历序列
    • 6. 二叉树中和为某一值的路径
    • 7. 二叉树的深度
    • 8. 平衡二叉树
    • 9. 二叉树的下一个结点
    • 10. 对称的二叉树
    • 11. 按之字形顺序打印二叉树
    • 12. 把二叉树打印成多行
    • 13. 序列化二叉树
    • 14. 二叉搜索树的第k个结点

二叉树部分

本博客对剑指offer中的二叉树类型的题目进行总结和整理,并分析各类题目中容易出错的点和容易遗忘的边界条件等。

0. 构建二叉树

在练习剑指offer题目之前,先学习一下如何基于一个list构建二叉树。

思路:递归。
公式:节点i的左节点为(2*i+1),右节点为(2i+2)。(i从0开始)

# -*- coding:utf-8 -*-# '二叉树结点类'
# '二叉树结点类'
class TreeNode:def __init__(self, x):self.val = xself.left = Noneself.right = None# '列表创建二叉树'
def listcreattree(root,llist,i):###用列表递归创建二叉树,#它其实创建过程也是从根开始a开始,创左子树b,再创b的左子树,如果b的左子树为空,返回none。#再接着创建b的右子树,if i < len(llist):if llist[i] == '#':return Noneelse:root = TreeNode(llist[i])print('列表序号 {}, the value is {}'.format( i, llist[i]))root.left = listcreattree(root.left, llist, 2*i+1)root.right = listcreattree(root.right, llist, 2*i+2)print('return the root: {}'.format(root.val))return rootreturn rootllist=['1','2','3','#','4','5']
listcreattree(None,llist,0)

1. 重建二叉树

题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

示例:
输入:[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值:{1,2,5,3,4,6,7}

思路:递归。1. 根据前序遍历的第一个结点确定根结点 2. 根据根结点在中序遍历中找到左右子树及其长度,并将前序和中序遍历中的左右子树分开 3. 对左右子树的前序和中序遍历进行相同的操作,进行递归。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:# 返回构造的TreeNode根节点def reConstructBinaryTree(self, pre, tin):# 首先处理特殊情况,即树为空或只有一个结点if len(pre) == 0:returnif len(pre) == 1:return TreeNode(pre[0])else:root = TreeNode(pre[0])# 对左子树递归root.left = self.reConstructBinaryTree(pre[1:tin.index(pre[0])+1], tin[0:tin.index(pre[0])])# 对右子树递归root.right = self.reConstructBinaryTree(pre[tin.index(pre[0])+1:], tin[tin.index(pre[0])+1:])return root

2. 树的子结构

题目描述:
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

示例:
输入:{8,8,#,9,#,2,#,5},{8,9,#,2}
返回值:true

思路:这里存在两重递归。 1. 首先在A树的结点中递归地寻找和B树根结点相同的结点 2. 找到根结点以后,递归地判断该A树中结点的左右子树是否和B树中的结点一一对应。如果不对应,则继续递归地寻找下一个相同的根结点。

class Solution:def HasSubtree(self, pRoot1, pRoot2):# write code hereif pRoot1 is None or pRoot2 is None:return Falseresult = Falseif pRoot1.val == pRoot2.val:result = self.isSubtree(pRoot1, pRoot2)if result == False:result = self.HasSubtree(pRoot1.left, pRoot2) | self.HasSubtree(pRoot1.right, pRoot2)return resultdef isSubtree(self, root1, root2):if root2 is None:return Trueif root1 is None:return Falseif root1.val == root2.val:return self.isSubtree(root1.left, root2.left) & self.isSubtree(root1.right, root2.right)return False

3. 二叉树的镜像

题目描述:操作给定的二叉树,将其变换为源二叉树的镜像。

示例:
输入:{8,6,10,5,7,9,11}
返回值:{8,10,6,11,9,7,5}

思路:新建一个相同的根结点,然后进行递归:该结点的左孩子等于原结点的右孩子,该结点的右孩子等于原结点的左孩子。

class Solution:def Mirror(self, pRoot):# write code hereif pRoot is None:return Noneroot = TreeNode(pRoot.val)root.left = self.Mirror(pRoot.right)root.right = self.Mirror(pRoot.left)return root

4. 从上往下打印二叉树

题目描述:从上往下打印出二叉树的每个节点,同层节点从左至右打印。

示例:
输入:{5,4,#,3,#,2,#,1}
返回值:[5,4,3,2,1]

思路:树+队列。循环的方法。

  1. 新建两个空队列(list)作为结点队列和值队列。首先将根结点压入结点队列
  2. 当结点队列非空时,进行循环:弹出队列的队头结点, 将该结点的值存入值队列。将该结点的左右子结点依此压入结点队列。直到结点队列为空,所有的结点的值均被存入值队列。
class Solution:# 返回从上到下每个节点值列表,例:[1,2,3]def PrintFromTopToBottom(self, root):# write code herequeue = []result = []if root is None:return resultqueue.append(root)while queue:cur_Node = queue.pop(0)result.append(cur_Node.val)if cur_Node.left is not None:queue.append(cur_Node.left)if cur_Node.right is not None:queue.append(cur_Node.right)return result

5. 二叉搜索树的后序遍历序列

题目描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。(ps:我们约定空树不是二叉搜素树)

示例:
输入:[4,8,6,12,16,14,10]
返回值:true

思路
二叉搜索树的特点:左子树 < 根结点 < 右子树
后序遍历的特点:元素先后顺序是[ [左子树元素] [右子树元素] [根结点] ],即根结点在最后。
编程逻辑:

  1. 取数组最后一个元素作为根结点的值。
  2. 从左往后找出小于根结点的元素及其索引i。然后判断i之后的元素(不包括根结点)是否小于根结点,不满足则返回False。
  3. 重复上述步骤,进行递归。若左子树元素存在,递归左子树;若右子树元素存在,递归右子树。当左子树和右子树都返回True时,最终结果为True.
# -*- coding:utf-8 -*-
class Solution:def VerifySquenceOfBST(self, sequence):# write code hereif not sequence:return Falseroot = sequence[-1]# 找出左子树和右子树的分界线for i in range(len(sequence)):if sequence[i] > root:break# 验证右子树是否满足后序遍历for j in range(i, len(sequence)-1):if sequence[j] < root:return False# 开始递归,重复上述步骤,先递归左子树,再递归右子树left = Trueif i >0:   #i > 0 的时候证明有左孩子,i<0就证明没有。说明左树已经遍历完毕left = self.VerifySquenceOfBST(sequence[:i])right = True# 通过i的值不在最后一个结点判断是否有右孩子,len(sequence) - 1 为sequence的最后一个结点.如果i已经在最后一个节点了,则不进行判断if i < len(sequence)-1:right = self.VerifySquenceOfBST(sequence[i:-1])return left and right

6. 二叉树中和为某一值的路径

问题描述:输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

示例1
输入:{10,5,12,4,7},22
返回值:[[10,5,7],[10,12]]
示例2
输入:{10,5,12,4,7},15
返回值:[ ]

思路一

  1. 深度优先搜索,找出所有的路径。(注意,当root.left和root.right同时为None时,该结点为叶子节点,此时保存路径。)
  2. 然后遍历地对每一条路径求和,找出所有符合条件的。
import copy
class Solution:# 返回二维列表,内部每个列表表示找到的路径def FindPath(self, root, expectNumber):# write code hereif not root:return []path = []result = []expect = []result = self.collectPath(root, path, result)for i in range(len(result)):if sum(result[i]) == expectNumber:expect.append(result[i])return expectdef collectPath(self, root, path, result):if not root:return path.append(root.val)self.collectPath(root.left, path, result)self.collectPath(root.right, path, result)# 当到达叶子节点时,保存完整路径if not root.left and not root.right:result.append(copy.deepcopy(path))# 弹出叶子节点,回到上一级节点path.pop()   return result

思路二:更加简洁!!推荐!
在进行深度优先搜索(dfs)时,就对路径之和进行筛选,符合条件的保存。

class Solution:# 返回二维列表,内部每个列表表示找到的路径def FindPath(self, root, expectNumber):# write code hereself.expectNumber = expectNumberres = []if not root:return res       # 注意,当树为空时,返回[],而不是Noneself.dfs(root, res, [root.val])return resdef dfs(self, root, res, path):if not root.left and not root.right and sum(path) == self.expectNumber:res.append(path)if root.left:# 注意这里的 path+[root.left.val] 的写法,省去了思路一中的path.pop()操作self.dfs(root.left, res, path+[root.left.val])  if root.right:self.dfs(root.right, res, path+[root.right.val])

7. 二叉树的深度

题目描述:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

示例1
输入:{1,2,3,4,5,#,6,#,#,7}
返回值:4

思路:递归。(递归的终点都是回到条件1或2)

  1. 若根结点为空,深度为0.
  2. 若根结点无左右结点,则深度为1.
  3. 若根结点只有左结点(或只有右结点),深度=1+ 左子树深度(或右子树深度)
  4. 若根结点既有左结点,又有右结点,深度=1+ max(左子树深度,右子树深度)

2,3都可以视为4的特例,故可以合并到4。

class Solution:def TreeDepth(self, pRoot):# write code hereif pRoot is None:return 0else:return 1 + max(self.TreeDepth(pRoot.left), self.TreeDepth(pRoot.right))

8. 平衡二叉树

题目描述:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

示例
输入:{1,2,3,4,5,6,7}
返回值:true

思路:先判断根结点处,左右子树的高度差是否满足条件;然后依此递归左右子树是否满足平衡二叉树条件。

class Solution:def IsBalanced_Solution(self, pRoot):# write code hereif not pRoot:return Trueif abs(self.depth(pRoot.left) - self.depth(pRoot.right)) > 1:return Falseif pRoot.left:self.IsBalanced_Solution(pRoot.left)if pRoot.right:self.IsBalanced_Solution(pRoot.right)return Truedef depth(self, root):if not root:return 0else:return 1 + max(self.depth(root.left), self.depth(root.right))

9. 二叉树的下一个结点

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

思路:中序遍历中结点的下一个结点有三种情况,
1)二叉树为空,返回None
2)当该结点的右孩子存在时,则从该结点的右孩子出发对右子树进行中序遍历找到叶子节点为止;
3)当该结点的右孩子不存在时,则往该结点的父结点回溯。
首先判断是否为根结点,若是则返回None。若不是,则继续判断该结点是否为其父结点的左孩子,若为左孩子,则下一个结点为其父结点,若为右孩子,则继续往上遍历父结点的父结点,重复上述判断。

class Solution:def GetNext(self, pNode):# write code hereif pNode.right is None: # 无右孩子,则往父结点回溯return self.getnext(pNode)else:          # 有右孩子,则对右孩子进行中序遍历,找到第一个结点return self.getleft(pNode.right)def getnext(self, Node):    # search the parent nodeif Node.next is not None:    # 不是根结点if Node.next.left != Node: # 该结点为父结点的右孩子,继续向上遍历,重复判断return self.getnext(Node.next)else:             # 该结点为父结点的左孩子,下一个结点为父结点return Node.nextelse:return Nonedef getleft(self, Node):    # search the left leaf node of the right subtreewhile Node.left is not None:Node = Node.leftreturn Node

写法二:用循环来代替递归,更加简洁

class Solution:def GetNext(self, pNode):if not pNode:    # 树为空树时return None if pNode.right: # 结点的右孩子存在pNode = pNode.rightwhile pNode.left: # 一直向左遍历pNode = pNode.leftreturn pNodeelse:          # 结点的右孩子不存在,向父结点遍历while pNode.next:if pNode == pNode.next.left: #判断该结点是否为父结点的左孩子,若是则返回父结点,否则继续往上遍历return pNode.nextpNode = pNode.nextreturn None

10. 对称的二叉树

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

示例1
输入: {8,6,6,5,7,7,5}
返回值:true
示例2
输入:{8,6,9,5,7,7,5}
返回值:false

思路

class Solution:def isSymmetrical(self, pRoot):if pRoot is None:return Truereturn self.isSame(pRoot.left, pRoot.right)def isSame(self, left, right):if not left and not right: # 若左孩子和右孩子均为空,则返回Truereturn Trueif not left or not right: # 若左孩子和右孩子只有一个为空,则非对称return Falseif left.val == right.val:  # 若左孩子结点=右孩子结点,则继续向下遍历return self.isSame(left.left, right.right) and self.isSame(left.right, right.left)else:return False

11. 按之字形顺序打印二叉树

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

示例1
输入:{8,6,10,5,7,9,11}
返回值:[[8],[10,6],[5,7,9,11]]

思路:首先进行层序遍历,然后打印时奇顺偶逆。

class Solution:def Print(self, pRoot):# write code hereif not pRoot:return []      #注意,这里输出空list,而不是Nonesta = []lists = []index = 0          # 记录层数的奇偶sta.append(pRoot)while sta:res = []length = len(sta)for i in range(length):Node = sta.pop(0)res.append(Node.val)if Node.left:sta.append(Node.left)if Node.right:sta.append(Node.right)if index % 2 == 0:    # 偶数层顺序,奇数层逆序lists.append(res)else:res.reverse()lists.append(res)index += 1return lists     # 当没有return时会报错:[object of type 'NoneType' has no len()]

12. 把二叉树打印成多行

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

示例1
输入:{8,6,10,5,7,9,11}
返回值:[[8],[6,10],[5,7,9,11]]

思路:和上一题类似,利用队列进行二叉树的层序遍历!

class Solution:# 返回二维列表[[1,2],[4,5]]def Print(self, pRoot):# write code herestack1 = []stack2 = []if not pRoot:return stack2stack1.append(pRoot)while stack1:layer = []l = len(stack1)for i in range(l):Node = stack1.pop(0)layer.append(Node.val)if Node.left:stack1.append(Node.left)if Node.right:stack1.append(Node.right)stack2.append(layer)return stack2

13. 序列化二叉树

题目描述:
请实现两个函数,分别用来序列化和反序列化二叉树。

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

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

例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树。

示例1
输入:{8,6,10,5,7,9,11}
返回值:{8,6,10,5,7,9,11}

思路
序列化二叉树:将结点值逐个取出保存为字符串。我们这里选择先序遍历的方式进行序列化,value字符之间用 “,”隔开。需要注意的是,如果遇到空结点,则用“#”表示。
反序列化:首先将字符串切分成list, 然后按照序列化的遍历方式重构二叉树。

class Solution:# 序列化,这里按照先序遍历存储def Serialize(self, root):# write code hereif root is None:return '#'return str(root.val) + ',' + self.Serialize(root.left) + ',' + self.Serialize(root.right)# 反序列化,根据序列化所采用的遍历顺序,重构二叉树def Deserialize(self, s):# write code herelist0 = s.split(',')   # 将字符串切分成listreturn self.reContructTree(list0)def reContructTree(self, list0):value = list0.pop(0)root = None         # important! Don't forget!if value != '#':root = TreeNode(int(value))root.left = self.reContructTree(list0)root.right = self.reContructTree(list0)return root

14. 二叉搜索树的第k个结点

题目描述:给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点。

示例1
输入:{5,3,7,2,4,6,8},3
返回值:{4}
说明:按结点数值大小顺序第三小结点的值为4

思路一: 按照二叉搜索树的左、中、右顺序进行遍历。设定一个全局计数器,每遍历一个结点,计数器+1。设定计数器==k为递归的出口,符合条件时,递归结束。(没有多余的计算,推荐思路一。)

class Solution:# 返回对应节点TreeNodedef __init__(self):self.idx = 0def KthNode(self, pRoot, k):# write code hereif pRoot is None or k <= 0: # 若树为空或k<0,则返回Nonereturn None# 中序遍历。特殊的一点是,在中间输出结点时,判断这是第几个结点。node = self.KthNode(pRoot.left, k) # 向左遍历,直到pRoot.left为空(pRoot为叶子节点或pRoot无左孩子)if node:            #向左遍历到尽头,返回结点,全局计数器self.idx += 1return nodeself.idx += 1if self.idx == k:   # 当全局计数器==k时,返回该结点(该位置为递归的出口,重要!)return pRootnode = self.KthNode(pRoot.right, k) # 向右遍历,重复上述步骤return node

思路二:简单粗暴法。先来一遍中序遍历,在list中存储所有的值,然后打印第k个。(有多余的遍历!)

    def KthNode(self, pRoot, k):# write code hereif (pRoot is None) or (k <= 0):return Noneres = []self.inorder(pRoot, res)if k > len(res):return Nonereturn res[k-1]def inorder(self, node, res):  # 中序遍历if node is None:returnself.inorder(node.left, res)res.append(node)self.inorder(node.right, res)

暂时先整理到这里,想到什么再回来补充!
经验不足,如发现有遗漏或思路不对之处,欢迎大家留言指出。如果大神们愿意指教更简洁的解法,更加感激不尽!


备注:如若转载,请注明出处!

剑指offer-二叉树(python)相关推荐

  1. 剑指offer(Python版本)--精心整理

    剑指offer(Python版本) 1.二维数组的查找 2.替换空格 3.从尾到头打印链表 4.重建二叉树 5.用两个栈实现队列 6.旋转数组的最小数字 7.斐波那契数列 8.跳台阶 9.变态跳台阶 ...

  2. 剑指offer python实现_剑指offer系列python实现 日更(三)

    今天来讲讲斐波那契数列和它的孩子们~先讲个冷笑话:今天来一盘斐波那契炒饭,它等于昨天的炒饭加上前天的炒饭 ‍ 7.斐波那契数列 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第 ...

  3. 剑指offerpython_剑指offer系列python实现 日更(一)

    写在开头,这个专栏是记录刷题/学习/求职面试的小号专栏.主要方向约等于是混口饭吃干啥都行,希望能够在学习的过程中有所输出,也能帮助到别人~ 废话少说,首先是经典的<剑指offer>系列,题 ...

  4. 剑指offer有python版吗_剑指Offer算法类题目[Python版]

    标签:重复   作用   coding   面试   medium   mba   none   fas   utf-8 面试题012 数值的整数次方 解题思路1 考虑所有情况,循环连乘 代码: de ...

  5. C#刷剑指Offer | 二叉树中和为某一值的路径

    [C#刷题]| 作者 / Edison Zhou 这是EdisonTalk的第292篇原创内容 我们来用之前学到的数据结构知识来刷<剑指Offer>的一些核心题目(精选了其中30+道题目) ...

  6. 剑指offer 二叉树的深度

    剑指offer 牛客网 二叉树的深度 # -*- coding: utf-8 -*- """ Created on Wed Apr 10 09:29:36 2019@au ...

  7. @ 剑指offer(python)表示数值的字符串

    剑指offer刷题笔记53(python) 题目描述 请实现一个函数用来判断字符串是否表示数值(包括整数和小数).例如,字符串"+100","5e2",&quo ...

  8. 剑指offer有用python版的吗_Python算法面试通关,剑指offer就靠它了

    原标题:Python算法面试通关,剑指offer就靠它了 北上广容不下肉身, 三四线放不下灵魂, 程序员里没有穷人, 有一种土豪叫算法工程师. 算法,晦涩难懂,却又是IT领域最受重视的素养之一可以说, ...

  9. 剑指offer全集python(3/3)第三大部分

    目录 45.圆圈后最后剩下额数 46.求1,2...n的和 47.不用加减乘除做相加 49.将字符串转换成整数 50.数组中重复的数字 51.构建乘积数组 52.正则表达式匹配 53.表示数值的字符串 ...

  10. [剑指Offer]-二叉树的深度

    题目描述(一) 输入一棵二叉树的根结点,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度.例如下图中的二叉树的深度为4,因为它从根结点到叶结点最长 ...

最新文章

  1. 小白给小白详解维特比算法(二)
  2. os.clock()导致的bug
  3. html5画布可以p图,HTML5图像适合发布在画布上
  4. 显卡在电脑什么位置_告诉你什么配置的电脑显卡/GPU才能播放4K电影视频
  5. Matlab获取新浪财经实时行情
  6. 华三云:不做开源的投机者
  7. 记一次简单的vue组件单元测试
  8. 简单两步快速实现shiro的配置和使用,包含登录验证、角色验证、权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)...
  9. Apache Mina的用法
  10. 树莓派 | Debian更改屏幕分辨率 或 解决树莓派使用HDMI-VGA转换器黑屏的方案
  11. 02 JS实现时钟效果
  12. Spring Boot 集成 Prometheus
  13. 小米笔记本Air 13.3 i5-8250U macOS黑苹果efi引导文件
  14. STM32驱动BH1750模块
  15. VueHub:我用 ChatGPT 开发的第一个项目,送给所有 Vue 爱好者
  16. python-编码实现指数平滑法移动平均法
  17. 易經大意(1) 三和 韓長庚 著24
  18. Threejs系列--9游戏开发--沙漠赛车游戏【基础场景渲染】
  19. 考研线性代数常见概念、问题总结
  20. GPS便携机加装导航并设置端口波特率

热门文章

  1. 【Python】文件选择框选择文件
  2. 登录后跳转又提示未登录
  3. python线程锁和线程池
  4. Connor学Android - JNI和NDK编程
  5. okio 原理分析(一)
  6. 面对问题时如何解决呢---pytharm不能进行单步调试
  7. Matlab Fmincon 解决带积分的二元非线性规划问题
  8. 身为IT人你应该知道的几个威客网站【转】
  9. 健康人寿保险服务平台
  10. 哔哩哔哩在Hilt组件化的使用 | 技术探索