leetcode 树相关问题

使用递归和遍历解耦树问题

树的创建与基本的遍历,这是后面大部分问题的基础

创建树

class Node:
    def __init__(self,val):
        self.val = val
        self.right = None
        self.left = None

    def __repr__(self):
        return f'val: {self.val}, left: {self.left}, right: {self.right}'

class Tree:
    def __init__(self):
        self.root = None

    def add_element(self,node_val):
        node = Node(node_val)
        if self.root == None:
            self.root = node
            return
        queue = [self.root]
        while True:
            pop_node = queue.pop(0)
            if pop_node.left is None:
                pop_node.left = node
                return
            else:
                queue.append(pop_node.left)
            if pop_node.right is None:
                pop_node.right = node
                return
            else:
                queue.append(pop_node.right)

新建树结构:

tree =Tree()
for i in range(1,8):
    tree.add_element(i)
  1. pytrhon类内方法__repr__,可以支持类的可视化;
  2. 问题的解耦,Node叶子,包含了值和左右的节点,而Tree树包含了一个队列(先进先出)辅助构建整个树结构。
  3. 树建设好后不需要依靠队列,在内存中依靠指针构成图的数据结构。
  4. Tree类建设好后,该类内的全局变量一直被保存,每次搜索都是从root节点开始,[root]——>pop_node = root——>[root.right,root.left]——>pop_node=root.left——>[root.right,root.left.right,root.left.left],pop(0)之后会依次追加pop_node的left和right,让每一层满了之后才下一层。
  5. 每次左右都会遍历到,只是有先后次序关系,所以才会让所有的节点都安顺序遍历到。

遍历

bfs

def bfs(self):
    if self.root is None:
        return
    queue = [self.root]
    while queue:
        pop_node = queue.pop(0)
        print(pop_node.val,end=' ')
        if pop_node.left is not None:
            queue.append(pop_node.left)
        if pop_node.right is not None:
            queue.append(pop_node.right)
  1. 与层次化创建一样,依靠一个队列,打印当前节点,把左节点和右节点放进来准备下次的打印。

递归形式的遍历

都是先左再右,前中后只的是根前中后打印。

predfs

根左右

def predfs(self,root):
    if root is None:
        return
    print(root.val,end=' ')
    self.predfs(root.left)
    self.predfs(root.right)
indfs

左根右

def indfs(self,root):
    if root is None:
        return
    self.indfs(root.left)
    print(root.val, end=' ')
    self.indfs(root.right)
afterdfs

左右根

def afterdfs(self,root):
    if root is None:
        return
    self.afterdfs(root.left)
    self.afterdfs(root.right)
    print(root.val, end=' ')

循环形式的遍历(栈)

pre_stack
def pre_stack(self):
    '''
    root left right,因为是stack所以倒着入,出root打印,然后入right,再left
    :return:
    '''
    if self.root is None:
        return
    stack = [self.root]
    while stack:
        pop_node = stack.pop()
        print(pop_node.val,end=' ')
        if pop_node.right is not None:
            stack.append(pop_node.right)
        if pop_node.left is not None:
            stack.append(pop_node.left)
in_stack
def in_stack(self):
    stack = []
    cur = self.root
    #原因为了能让循环开始,开始的时候stack是空的
    while stack or cur:
        while cur:
            stack.append(cur)
            cur = cur.left
        pop_node = stack.pop()
        print(pop_node.val,end=' ')
        cur = pop_node.right
    return
after_stack
 def after_stack(self):
     # left right root ,考虑第一种实现非常方便,所以倒序过来
     if self.root is None:
         return
     stack = [self.root]
     res = []
     while stack:
         pop_node = stack.pop()
         # print(pop_node.val,end=' ')
         res.append(pop_node)
         if pop_node.left is not None:
             stack.append(pop_node.left)
         if pop_node.right is not None:
             stack.append(pop_node.right)
     print(res[::-1])
     return
  1. 建设树的过程是队,遍历使用栈,这里把队和栈的行为表现的非常明白。
    (1).队列公平的,循环pop(0)依次把pop_node子节点(左、右)的信息入栈,体现了bfs的公平性,本层遍历满后才会继续进行。
    (2).栈的特点是“先紧着自己人做完的原则”,以前序为例,入栈顺序:right,left。所以,先pop()出栈顶的root,此时left就是当前的root整个循环串接起来,直到把所有关于left的内容都遍历结束,开始遍历栈底的right(这个right在开始的时候就已经入栈)这里关键是对树的结构依靠了栈的方式进行了遍历,从root的地方把right和left入进来,而后开始循环注意是出栈的时候才把这个节点的相关节点入栈。
  2. 中序遍历最复杂,关键是这个方法不是先出栈root节点。需要先找到最左边节点。所以除了依靠一个stack,还要依靠一个cur指针。
  3. 内层的while找到最左边的节点后出栈指向该节点的right,再入循环(可能是左子树的右节点),使用cur很妙的地方在于如果没有right接点直接对stack进行pop。代码很自然的完成了left root right的任务(包括了左节点还有右节点的case),所以还是先把整个结构构建起来,细枝末节的任务就容易了

二叉树的恢复

从前序与中序遍历中恢复二叉树 leetcode 105

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not preorder or not inorder:
            return
        root = TreeNode(preorder[0])
        index = inorder.index(root.val)
        root.left = self.buildTree(preorder[1:index+1],inorder[:index])
        root.right = self.buildTree(preorder[index+1:],inorder[index+1:])
        return root
  • 必须要有中序这个信息,否则恢复不出来,除非是完全二叉树。
  • 一定要先知道递归的输入和输出是什么,输入是两个列表,输出是输的root

从后序与中序遍历中恢复二叉树 leetcode 106

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        if not inorder or not postorder:
            return
        root = TreeNode(postorder[-1])
        index = inorder.index(root.val)
        root.left = self.buildTree(inorder[:index],postorder[:index])
        root.right =  self.buildTree(inorder[index+1:],postorder[index:-1])
        return root
  • 只要是任意一个list为空则是建树完成。
  • 然后倒换着查看序号即可。

将有序数组转换为二叉搜索树 lc 108

在这里插入图片描述

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
        if len(nums)==0:
            return 
        mid = len(nums)//2
        root = TreeNode(nums[mid])
        root.left = self.sortedArrayToBST(nums[:mid])
        root.right = self.sortedArrayToBST(nums[mid+1:])
        return root
  • 是用数组构成树的常规操作,找到一个位置作为root,然后去划分数组变成左右节点。
  • 难点是找到中间这个值划分就可以形成一个平衡的BST。

有序链表转换二叉搜索树 lc 109

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def sortedListToBST(self, head: ListNode) -> TreeNode:
        def findmid(start,end):
            slow = fast = start
            while fast!=end and fast.next!=end:
                slow = slow.next
                fast = fast.next.next
            return slow

        def buildTree(start,end):
            if start==end:
                return
            mid = findmid(start,end)
            root = TreeNode(mid.val)
            root.left = buildTree(start,mid)
            root.right = buildTree(mid.next,end)
            return root
        return buildTree(head,None)
  • 和上一题一样,但是对链表的操作。找重点需要借助快慢指针,其次是建立树的过程是一个显著的二分过程,还是首尾指针,python的list封装太好感觉不出来。

二叉搜索树的最近公共祖先 lc 235

在这里插入图片描述

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root:
            return 
        if p.val>root.val and q.val>root.val:
            return self.lowestCommonAncestor(root.right,p,q)
        elif p.val<root.val and q.val<root.val:
            return self.lowestCommonAncestor(root.left,p,q)
        else:
            return root
  • 因为是BST所以大小是知道的,只不过有两个节点有点麻烦,就同时比较就可以,要不舍弃。
  • 看这个bst的结构确实是如上所述的代码。

两个节点的最近公共节点 leetcode 236

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root:
            return
        if p==root or q==root:
            return root

        # 返回的仅仅是节点,但是不是公共,递归只完成节点的任务
        leftnode = self.lowestCommonAncestor(root.left,p,q)
        rightnode = self.lowestCommonAncestor(root.right,p,q)
        
        # 公共是下面的逻辑,在哪里,如同归并排序,上面分段,下面是排序
        if not leftnode and not rightnode:
            return
        if leftnode and not rightnode:
            return leftnode
        if not leftnode and rightnode:
            return rightnode
        if leftnode and rightnode:
            return root
  • 这种方法和归并排序是一样的,上面负责一路深入下去,在递归之后是具体的最小子操作,就是到了叶子的底部开始比较可能性了。
  • 把问题想清楚,目前的问题是两个节点p,q可能出现在树中的情况是什么,到底了,在一顺,在两边,都不在。
  • 先把问题写出来,缩小问题范围,之后就使用它好了。

最长同值路径 lc 687

给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。注意:两个节点之间的路径长度由它们之间的边数表示。

在这里插入图片描述

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def longestUnivaluePath(self, root: TreeNode) -> int:
        def helper(root):
            if not root:
                return 0
            leftCount = helper(root.left)
            rightCount = helper(root.right)
            left,right = 0,0
            if root.left is not None and root.left.val == root.val:
                left = leftCount + 1
            if root.right is not None and root.right.val == root.val:
                right = rightCount + 1
            self.res = max(self.res,left+right)
            return max(left,right)

        self.res = 0
        helper(root)
        return self.res
  • 和上一题有类似,想给出递归的形式和返回值,然后后面利用这个东西,本身返回的是单边的最大值。但是要的res是可以左右之和的打不了一边为0.

归并两个树 lc 617

把两个树对应的位置值相加,形成新的一个树

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:
        if not t1:
            return t2
        if not t2:
            return t1

        root = TreeNode(t1.val+t2.val)
        root.left = self.mergeTrees(t1.left,t2.left)
        root.right = self.mergeTrees(t1.right,t2.right)
        return root

树的回溯问题

二叉树的所有路径 leetcode 257

输入:

   1
 /   \
2     3
 \
  5

输出: ["1->2->5", "1->3"]

解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def binaryTreePaths(self, root: TreeNode) -> List[str]:

        def dfs(root,path,res):
            if not root:
                return

            path += str(root.val)
            if not root.left and not root.right:
                res.append(path)
                return
            path+='->'
            dfs(root.left,path,res)
            dfs(root.right, path, res)
        path=''
        res =[]
        dfs(root,path,res)
        return res
  • 最重要的是树的回溯方法,解空间只有两个,左和右,所以不用写循环去遍历,因为只有两个,如果root的左和右都为空的时候,则已经到底了
  • 输出的格式也有点困难,是字符串,而且还要带个->,所以直接用空字符串去接它,这也是本题目的精髓所在,用结束条件去截止一下。文本也有类似的参考价值。如果还有后续再加上这个东西。这个题目比较特别,如果不是一个的输出,pop很难定位。所以这个特殊问题记住用str。也避免了pop,每一次正好都回去了,而且都是新值。
  • 不用pop因为path是字符串,所以每次的实际地址都不同,python可变变量和不可变变量

找寻二叉树和为给定数字的所有路径 剑指offer 34

import copy
class Solution:
    def pathSum(selfs,root,sum):
        def dfs(root,path,res,sum):
            if not root:
                return
            if not root.left and not root.right and sum == root.val:
                #判断是树走到头的方法
                #深刻的理解回溯,之前是sum==0是因为不需要走到头,减到0就行了,现在是走到头且最后一个数字是该数字
                path.append(root.val)
                res.append(copy.deepcopy(path))
                path.pop()
                # 注意找到之后就要返回,不继续了
                return
            #现在的解空间只有左和右,所以循环变成两个分开写
            # 方法解耦,这只是加入值,往左右走交给dfs
            path.append(root.val)
            # 这要往下减东西
            dfs(root.left,path,res,sum - root.val)
            path.pop()
            path.append(root.val)
            dfs(root.right, path, res, sum - root.val)
            path.pop()

        path =[]
        res = []
        dfs(root,path,res,sum)
        return res
  • 之前的回溯是所有的解空间,现在解空间就两个,所以不用写循环就直接分开写两次就行了。
  • path是可变的变量,只要加入就要pop,所以上面那涉及到append的就pop。
  • 找到根节点的判断方法。

路径总和 lc 112

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:
        if not root:
            return False
        
        def dfs(root,sum_):
            if not root:
                return
            if not root.left and not root.right and sum_==root.val:
                res[0] = True
                return True 
            
            dfs(root.left,sum_-root.val)
            dfs(root.right,sum_-root.val)
        
        res = [False]
        dfs(root,targetSum)
        return res[0]

相同的树 lc 100

给两个树,判断是否是相同的树

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        if p and q:
            return p.val == q.val and self.isSameTree(p.left,q.left) and self.isSameTree(p.right,q.right)
        return p == q

另一个树的子树 lc 572

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

子树 lc 572

判断一个树是否是另一个树的子树

class Solution:
    def isSeemtree(self,s,t):
        if s and t:
            return s.val == t.val and self.isSeemtree(s.left,t.left) and self.isSeemtree(s.right,t.right)
        return s == t
    def isSubtree(self, s: TreeNode, t: TreeNode) -> bool:
        if s and t:
        #关键是先判断一下是不是sametree,如果不是的话才左右的递归
            return self.isSeemtree(s,t) or self.isSubtree(s.left,t) or self.isSubtree(s.right,t) 
        return s == t
  • 需要用到sametree,sametree就是看写法是说每一个节点都要一样,如果一个有一个没有那也是False。
  • 子树是same的推广版本,允许部分相同,这就截住了,不一样再往下走,但是也是一定要走到底。不能出现图二的状态。

对称树 lc 101

在这里插入图片描述

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        def isSymmetric_(left,right):
            if left and right:
                return left.val == right.val and isSymmetric_(left.left,right.right) and isSymmetric_(left.right,right.left)
            else:
                return left == right
        return isSymmetric_(root,root)
  • 很巧妙的用两个输入,第一次输入的时候也符合这个范式,难点就在这正好同一了。
  • 这一系列题都是一样的,如果没东西了或者一半有就返回相等试试看。

二叉树的层序遍历 II lc 107

层次遍历外面while控制停止,内部的for控制层数
在这里插入图片描述

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []
        queue = [root]
        res = []
        while queue:
            tmp = []
            for _ in range(len(queue)):
                pop_node = queue.pop(0)
                tmp.append(pop_node.val)
                if pop_node.left:
                    queue.append(pop_node.left)
                if pop_node.right:
                    queue.append(pop_node.right)
            res.append(tmp)
        return res[::-1]
  • 使用队列就是bfs,要不就是dfs。
  • 因为是按照dfs的,所以一层有多个也能收进来。

103. 二叉树的锯齿形层序遍历

z形打印二叉树

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []
        ans = []
        queue = [root]
        turn = 1
        while queue:
            tmp = []
            for _ in range(len(queue)):
                node = queue.pop(0)
                tmp.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            if turn==1:
                ans.append(tmp)
            else:
                ans.append(tmp[::-1])
            turn = -turn
        return ans

找树左下角的值 lc 513

给定一个二叉树,在树的最后一行找到最左边的值。
在这里插入图片描述

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def findBottomLeftValue(self, root: TreeNode) -> int:
        if not root:
            return 
        queue = [root]
        while queue:
            for i in range(len(queue)):
                pop_node = queue.pop(0)
                if i == 0:
                    leftmost = pop_node.val
                if pop_node.left:
                    queue.append(pop_node.left)
                if pop_node.right:
                    queue.append(pop_node.right)
        return leftmost

  • 是通过一个简单思路方式取解决,肯定是bfs,最左和最右都很容易实现。

遍历求深度

树的最大深度 流程104

方法1,树大多数是这个套路

class Solution:
	def maxDepth(self,root):
		if root is None:
			return 0
		else:
			return max(self.maxDepth(root.left),self.maxDepth(root.right))+1

方法2,使用回溯,思路更自然:

class Solution:
    def maxDepth(self,root):
        def dfs(root,deep,res):
            if root == None:
                return
            if root.left == None and root.right == None:
                res.append(deep)
                return
            #很灵魂,意思是如果没到底部那就要+1
            deep += 1
            dfs(root.left,deep,res)
            dfs(root.right,deep,res)
        deep=1
        res=[0]
        dfs(root,deep,res)
        #因为遍历了所有最深的深度,所以返回max即可
        return max(res)

翻转二叉树 leetcode 226

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
    	if not root:
    		return
    	root.right,root.left = self.invertTree(root.left),self.invertTree(root.right)
    	return root
  • 注意这是python的语法,同时让两个内容进行互换。
  • 每一次都是以root为中轴进行翻转,所以依次进行递归。

平衡二叉树 lc 110

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
    #这个有没有这个判断无所谓
        #if not root:
            #return True
        def getDepth(root):
            if not root:
                return 0
            left_height = getDepth(root.left)
            right_height = getDepth(root.right)
			#有可能直接左边或者右边有一支深度失效,无需进一步往下走            
            if left_height == -1 or right_height==-1 or abs(left_height-right_height)>1:
                return -1
            return 1+max(left_height,right_height)
        return getDepth(root)!=-1
  • 二叉树最大深度的变种,只是加多了条件,不平衡的条件。每一次都要网上返回深度。

二叉树的最小深度 lc 111

从叶子节点到根节点的最小深度

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        if root.left and root.right:
            return min(self.minDepth(root.left),self.minDepth(root.right))+1
        else:
            return max(self.minDepth(root.left),self.minDepth(root.right))+1
  • 这个题目是理解树的问题,如果有两个叶子节点的话那肯定是两边都是最短的+1。
  • 如果变成单边了,那最短的,思考为一个完整的单支,则是最长+1。

二叉树的直径 lc 543

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

class Solution:
    def diameterOfBinaryTree(self, root):
        res = 0

        def maxDiameter(root):
            nonlocal res
            if not root:
                return 0
            
            l = maxDiameter(root.left)
            r = maxDiameter(root.right)
            res = max(res, l + r)
            return max(l, r) + 1
        
        maxDiameter(root)
        return res
        
#我习惯的写法
class Solution:
    def diameterOfBinaryTree(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """

        res = [0]
        def maxDiameter(root,res):
            if not root:
                return 0
            
            l = maxDiameter(root.left,res)
            r = maxDiameter(root.right,res)
            res[0] = max(res[0], l + r)
            return max(l, r) + 1
        
        maxDiameter(root,res)
        return res[0]
  • 很容易想到还是要计算深度的问题,只不过在每次计算出lr之后,都要算一下左右直径之和。
  • 每次的深度都是单边的。只要是深度的就要想到是深度,就是基础的树高度。只不过是如何拼凑在一起。

路径总和 3 lc 437

给定一个二叉树,它的每个结点都存放着一个整数值。找出路径和等于给定数值的路径总数。路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> int:
        if not root:
            return 0
        def dfs(root,sum):
            if not root:
                return 0
            res = 0
            if sum == root.val:
                res+=1
            #这不能return,因为树有正有负,下面还可能直接加成0还是对的。return就停下来了
                # return res
            res+=dfs(root.left,sum-root.val)
            res+=dfs(root.right,sum-root.val)
            #这里不可以这么写
            # res = dfs(root.left,sum-root.val)+dfs(root.right,sum-root.val)
            #可以这么写,因为左边和右边返回的是找到的结果,但是我要的是结果的
            # res += dfs(root.left,sum-root.val)+dfs(root.right,sum-root.val)
            return res
        
        return dfs(root,sum)+self.pathSum(root.left,sum)+self.pathSum(root.right,sum)
  • 与前面的题目不同的是无需从根开始,所以有最下面的递归。
  • 无需到叶子所以不需要root.left和root.right。
  • 最重要的内容:代码上的两个注释,尤其是res是要用来累加的。里面是int变量这样是我写的比较少的,要多思考一下。注意返回的是什么。
  • 如果是递归内部置0,需要不断的给自己加,如果是外部的话也需要加,注意的是每次递归执行的是找到的操作。

左叶子之和 lc 404

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def sumOfLeftLeaves(self, root: TreeNode) -> int:
        self.res = 0
        def dfs(root,sign):
            if not root:
                return
            if not root.left and not root.right and sign=='l':
                self.res+=root.val
                return
            #带标记很巧妙,树返回左和右
            dfs(root.left,'l')
            dfs(root.right,'r')

        dfs(root,'')
        return self.res
  • 是很容易想到要找到所有叶子节点,但是不太好找到所谓的“左”,怎么办那?去给带个标记。

二叉树中第二小的节点 lc 671

给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。
更正式地说,root.val = min(root.left.val, root.right.val) 总成立。
给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def findSecondMinimumValue(self, root: TreeNode) -> int:
    #这个min_val是当前最小值,需要return一个比该值大的值
        def helper(root,min_val):
            if not root:
                return -1
#每一次都优先去判断一下该值的关系
            if root.val > min_val:
                return root.val
#递归好思考之后的具体操作,每一枝都会有他的左右,即树的结构。            
            left = helper(root.left,min_val)
            right = helper(root.right,min_val)
#这是实质性的操作,找到左右之后要如果是有一个为空返回对侧,要不就返回两支的min
            if left==-1:
                return right
            if right==-1:
                return left
#每次都返回了左和右枝的最小值,但是还要求该最小值比当前的root.val大,所以min_val是不需要被改变的,一直是当前的这个值
            return min(left,right)
		
        return helper(root,root.val)
  • 简单的思路是直接找左边最小和右边最小的,但是都要比root大才是输出。

BST

修剪二叉搜索树 lc 669

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。

所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode:
        if not root:
            return
        root.left = self.trimBST(root.left,low,high)
        root.right = self.trimBST(root.right,low,high)
        if root.val>high:
            return root.left
        elif root.val<low:
            return root.right
        return root
  • 递归的思路,去左边右边取得一半的内容,然后得到内容后进行判断。

二叉搜索树中第K小的元素 lc 230

给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。

class Solution:
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        if not root:
            return
        cur = root
        stack = []
        cnt = 0
        while cur or stack:
            while cur:
                stack.append(cur)
                cur = cur.left
            popNode = stack.pop()
            cnt+=1
            if cnt==k:
                return popNode.val
            cur = popNode.right


#递归写法
class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        res = []
        def indfs(root):
            if root is None:
                return
            indfs(root.left)
            res.append(root.val)
            indfs(root.right)
        indfs(root)
        return res[k-1]
  • BST中序查找就是顺序,左中右,而且是升序,BST最重要的性质,所以直接找到然后去找k-1就可以。

把二叉搜索树转换为累加树 lc 538

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

在这里插入图片描述

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def convertBST(self, root: TreeNode) -> TreeNode:
        self.add = 0
        if not root:
            return 
        def reverse_inorder(root):
            if not root:
                return 
            reverse_inorder(root.right)
            self.add+=root.val
            root.val = self.add
            reverse_inorder(root.left)
        reverse_inorder(root)
        return root
  • 累加树是指当前节点加上所有比当前节点值小的值,如上图,BST的中序是升序,所以找它的逆序则是降序,每个位置位置都是加上自己当前的值,一路加下去,也不用dp,因为是单调的。直接在当前节点去改变值即可。
  • 这个题目乍一想太复杂,如果展开弄规则太多。是一个应用性质的题目。

两数之和 IV - 输入 BST lc 653

给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。
在这里插入图片描述

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def findTarget(self, root: TreeNode, k: int) -> bool:
        if not root:
            return False
       #注意第一次遍历的时候是空,没有节点被遍历     
        marked = set()
        stack = [root]

        while stack:
            pop_node = stack.pop()
            if pop_node:
                if k-pop_node.val in marked:
                    return True
                # 两数之和就是这个模版
                marked.add(pop_node.val)
                if pop_node.left:
                    stack.append(pop_node.left)
                if pop_node.right:
                    stack.append(pop_node.right)
        return False


# 两数之和,和开平方有点类似不完全一样
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        marked = set()
        for i in range(len(nums)):
            if target-nums[i] in marked:
                return [i,nums.index(target-nums[i])]
            marked.add(nums[i])

求树(BST)的差值最小的值 lc 530

给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。

class Solution:
    def getMinimumDifference(self, root: TreeNode) -> int:
        if not root:
            return

        l = []
        def indfs(root):
            if root is None:
                return
            indfs(root.left)
            l.append(root.val)
            indfs(root.right)
        indfs(root)

        #想法是中序遍历之后两两是最接近的,所以让两两做差
        min_val = float('inf')
        for i in range(1,len(l)):
            min_val = min(min_val,abs(l[i]-l[i-1]))
        return min_val
        #没必要这样浪费空间和时间。
        # l1 = [l[i] for i in range(1,len(l))]+[0]
        # l2 = [abs(l[i]-l1[i]) for i in range(len(l))]
        # return min(l2)
  • 求最小正好是两两是最小的,但是问题是没必要再开空间,直接每一次后面减去前面ok。
  • 求出来最小的,然后去遍历即可。

二叉搜索树中的众数 lc 501

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

#下面这个是通解,任意二叉树
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def findMode(self, root: TreeNode) -> List[int]:
	    if not root:
	        return
	    stack = [root]
	    adict = {}
	    while stack:
	        pop_node = stack.pop()
	        if pop_node.val not in adict:
	            adict[pop_node.val] = 1
	        else:
	            adict[pop_node.val]+=1
	        if pop_node.left:
	            stack.append(pop_node.left)
	        if pop_node.right:
	            stack.append(pop_node.right)
	    max_val = float('-inf')
	    for k,v in adict.items():
	        max_val = max(max_val,v)
	    res = []
	    for k,v in adict.items():
	        if v==max_val:
	            res.append(k)
	    return res

dp 打家劫舍 III lc 337

树,取root就不能取子节点,取子节点就不能取根。

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def rob(self, root: TreeNode) -> int:
        if not root:
            return 0
        def postdfs(root):
            if not root:
                return [0,0]
            left = postdfs(root.left)
            right = postdfs(root.right)
            val = [0,0]
            val[0] = max(left[0],left[1])+max(right[0],right[1])
            val[1] = left[0]+right[0]+root.val
            return val
        res = postdfs(root)
        return max(res)

#第二种解法
	def rob(self, root: TreeNode) -> int:
        if not root:
            return 0
        ns = {}
        s = {}
        def dfs(root):
            if not root:
                return
            dfs(root.left)
            dfs(root.right)
            s[root] = ns.get(root.left,0)+ns.get(root.right,0)+root.val
            ns[root] = max(s.get(root.left,0),ns.get(root.left,0))+ max(s.get(root.right,0),ns.get(root.right,0))
        dfs(root)
        return max(s[root],ns[root])  
  • 和背包也有点像。后续遍历,原因是左右之后汇总在root上做抉择。
  • 因为是一个list,所以对左右节点会有选和不选,不是打家劫舍1的操作。不同点在此。

不同的二叉搜索树 II lc 95

给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。
在这里插入图片描述

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        if n == 0:
            return []

        def helper(start,end):
            res = []
            if start>end:
                res.append(None)
                return res
            for i in range(start,end+1):
                leftlist = helper(start,i-1)
                rightlist = helper(i+1,end)
                for l in leftlist:
                    for r in rightlist:
                        root = TreeNode(i)
                        root.left = l
                        root.right = r
                        res.append(root)
            return res
        return helper(1,n)

Trie

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。 lc 208

Trie trie = new Trie();
trie.insert(“apple”);
trie.search(“apple”); // 返回 true
trie.search(“app”); // 返回 false
trie.startsWith(“app”); // 返回 true
trie.insert(“app”);
trie.search(“app”); // 返回 true

在这里插入图片描述

class  trie_node:
    def __init__(self):
        #每一个圈是一个dic,key是字母,v是指下去的节点
        self.children = dict()
        #不一定是最终的单词
        self.vaild = False

class Trie:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = trie_node()

    def insert(self, word: str) -> None:
        """
        Inserts a word into the trie.
        1.从root开始;
        2.如果一条边存在,顺着该边到下一个节点;
        3.如果边不存在则创建这个边;
        4.单词输入结束,vail=True
        """
        node = self.root
        for i in word:
            if i not in node.children:
            #跟常规的树的感觉不一样,但是也是类似的索引
                node.children[i] = trie_node()
            #存在就去这个节点指向的内容,不存在也创造出来了,可以过来了
            node = node.children[i]
        node.vaild = True

    def search(self, word: str) -> bool:
        """
        Returns if the word is in the trie.
        1.从root开始;
        2.如果一条边存在,顺着该边到下一个节点;
        3.如果边不存在return False
        4.单词输入结束,vail=True
        """
        node = self.root
        for i in word:
            if i not in node.children:
                return False
            node = node.children[i]
        return node.vaild

    def startsWith(self, prefix: str) -> bool:
        """
        Returns if there is any word in the trie that starts with the given prefix.
        """
        node = self.root
        for i in prefix:
            if i not in node.children:
                return False
            node = node.children[i]
        return True



# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)
  • 回忆一下我们之前学的前缀树的操作,该树是通过字典的形式维护一个树结构。每次每个节点都有一个是不是最终位置的标识符。
  • 最顶端的是空。每个节点都指向下一堆东西。看插入那段代码,字典里存的是下一个节点。
  • dict[字符]=类

键值映射 lc 677

实现一个 MapSum 类,支持两个方法,insert 和 sum:

在这里插入图片描述

class Node(object):
    def __init__(self, count = 0):
        self.children = collections.defaultdict(Node)
        self.count = count
        
class MapSum(object):

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = Node()
        self.keys = {}

    def insert(self, key, val):
        """
        :type key: str
        :type val: int
        :rtype: void
        """
        #这个操作是更新,可能要把之前的key的值更新一个新的数值,如果不是更新则是插入了一个新的值。
        delta = val - self.keys.get(key, 0)
        # 更新保存键值对的keys
        self.keys[key] = val
        
        curr = self.root
        # 更新节点的count
        curr.count += delta
        for char in key:
            curr = curr.children[char]
            curr.count += delta

    def sum(self, prefix):
        """
        :type prefix: str
        :rtype: int
        """
        curr = self.root
        for char in prefix:
            if char not in curr.children:
                return 0
            curr = curr.children[char]
        return curr.count

# Your MapSum object will be instantiated and called as such:
# obj = MapSum()
# obj.insert(key,val)
# param_2 = obj.sum(prefix)
  • 一种比较特殊的数据结构。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值