代码随想录算法训练营第十天 | 二叉树系列1

二叉树系列1

二叉树理论基础

先放上代码随想录的文章链接。

二叉树理论基础

注意点小记

二叉树的种类

1:完全二叉树底层不一定是满的,但是从左到右必须是连续的。
2:二叉搜索树:二叉搜索树的时间复杂度是O(logn),比如插入、查询。二叉搜索树对树的结构没有要求,对树的节点的顺序有一定要求。
3:满二叉树:满的
4、平衡二叉搜索树:左子树和右子树高度的绝对值的差不能大于1。

二叉树的存储方式

1:顺序存储,用一个数组去存储下标,如果父节点的数组下标是 i ,那么左孩子是 2i+1 , 右孩子是 2i+2 。在对二叉树进行顺序存储时,要先将二叉树扩充为完全二叉树,这样才满足左右孩子的计算方式。

2:顺序存储的方式很少用,一般均用链式存储,二叉树可以理解为就是一种链表,传入一个二叉树,就是传入一个链表。

二叉树的遍历

1:前序遍历、中序遍历、后序遍历:本质上均为深度优先搜索。先往深走,遇到叶子节点返回。DFS的实现大多用递归实现,而前中后序遍历,也均有两种实现方式(递归法、迭代法),用递归可以实现的,用栈也可以模拟递归的过程。

2:层序遍历:本质为广度优先搜索。实现上就是迭代法,用一个队列来实现,BFS的实现也是队列,先进先出的特性满足一层一层遍历的需求。

3:前中后序遍历,其实指的就是中间节点的遍历顺序。前–中左右,中:左中右,后:左右中。

要熟悉自己所用编程语言常用的数据容器的底层实现

下面是从网上找的一篇博客,讲的很详细。

使用Python实现常见的数据结构之原理讲解

一定要会自己实现所用数据结构的定义

这里是要会定义:二叉树的节点。(实现方式其实就是链表)

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

二叉树的递归遍历

递归遍历,学习如何写递归。递归三部曲将贯穿整个二叉树章节。

递归三部曲

1、确定递归函数的参数和返回值。(不必一次性就确定好,需要什么定义什么,二叉树章节一般为根节点+数组用于存储结果)
2、确定终止条件。
3、确定单层递归的逻辑。

前中后序递归遍历

放数组的操作即为:中
进左节点:左
进右节点:右
按照这个定义以及前中后序的逻辑,按顺序编写即可。

前序遍历–我的代码

对比之后还是觉得应该学习代码随想录的编写方式,不能因为函数中没有传入储存结果的变量,就去重新编写一个函数,还是对递归理解的不到位。

class TreeNode:
    def __init__(self,val,left=None,right=None):
        self.val = val
        self.left = left
        self.right = right
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        self.digui(root,res)
        return res

    def digui(self,root: Optional[TreeNode],res):
        if root == None :
            return
        res.append(root.val)
        self.digui(root.left,res)
        self.digui(root.right,res)

前序遍历–代码随想录的代码

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []

        left = self.preorderTraversal(root.left)
        right = self.preorderTraversal(root.right)

        return  [root.val] + left +  right

中序遍历–我的代码

接下来的遍历,不再额外编写一个带存储结果变量的递归函数。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []
        left = self.inorderTraversal(root.left)
        right = self.inorderTraversal(root.right)

        return left + [root.val] + right

后序遍历–我的代码

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []

        left = self.postorderTraversal(root.left)
        right = self.postorderTraversal(root.right)

        return left + right + [root.val]

二叉树的非递归遍历–迭代法

比较有难度!要好好理解。

代码随想录的讲解文章链接:
二叉树的迭代法遍历

递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

注意点记录

前序–中左右:入栈顺序为–中右左,因为栈是先入后出。

中序遍历和前序遍历,是完全不一样的,不能用相同的逻辑!

中序遍历:用一个指针来遍历节点,用栈来记录遍历过的元素。
在这里插入图片描述
后序遍历:可以通过更改前序代码来实现。
在这里插入图片描述

代码随想录强调

在用迭代法实现遍历的过程中,要明确两个概念:遍历节点和处理节点,因为前序遍历时,二者一致,才可以较为简洁地写出代码。

仔细思考前序遍历的顺序–中左右,以及前序遍历的迭代法的迭代顺序(这里要好好思考一下,中间节点处理之后,push左右进去,但是下一层循环只处理一个,也就是说,在左右均可遍历的情况下,push两个pop一个),可以发现完全一致!

一刷逻辑还没有理清,对迭代法的思路没有掌握

一刷没有自己写的代码。

代码随想录的代码

前序遍历:

class TreeNode:
    def __init__(self,val,left=None,right=None):
        self.val = val
        self.left = left
        self.right = right
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
       
        if root == None:
           return []
        stack  = [root]

        while stack != [] :
            node = stack.pop()
           
            res.append(node.val)

            if node.right :
                stack.append(node.right)
            if node.left :
                stack.append(node.left)
        return res

后序遍历:

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []
        res = []
        stack = [root]
        while stack != [] :
            node = stack.pop()
            res.append(node.val)

            if node.left :
                stack.append(node.left)

            if node.right :
                stack.append(node.right)
        res.reverse()
        return res

中序遍历:

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None:
            return []
        res = []
        stack = []
        cur = root
        while cur or stack :
            if cur :
                stack.append(cur)
                cur = cur.left
            else :
                cur = stack.pop()

                res.append(cur.val)

                cur = cur.right
        return res

我的代码(当天晚上自己写)

前序遍历:

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []
        stack = [root]
        res = []
        while stack :
            node = stack.pop()
            res.append(node.val)

            # 这里注意,因为所用的stack为先进后出,所以先序遍历:中左右
            # 是先加入右子树
            if node.right :
                stack.append(node.right)
            if node.left :
                stack.append(node.left)
        return res

后序遍历:

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []
        res = []
        stack = [root]
        while stack != [] :
            node = stack.pop()
            res.append(node.val)

            if node.left :
                stack.append(node.left)

            if node.right :
                stack.append(node.right)
        res.reverse()
        return res

中序遍历:(没写出来,错误代码)

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []

        stack = []
        res = []

        cur = root

        while cur or stack != [] :
            
            if cur == None :
                node = stack.pop()
                res.append(node.val)
                cur = node

            else :
                stack.append(cur.left)
                cur = cur.left

二叉树的非递归遍历–迭代法统一编写方式

有困难,一刷不会。

重点

重点在于明确,只对中节点操作,即放入res中。

代码随想录的代码

前序遍历:

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        result = []
        st= []
        if root:
            st.append(root)
        while st:
            node = st.pop()
            if node != None:
                if node.right: #右
                    st.append(node.right)
                if node.left: #左
                    st.append(node.left)
                st.append(node) #中
                st.append(None)
            else:
                node = st.pop()
                result.append(node.val)
        return result

中序遍历:

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        result = []
        st = []
        if root:
            st.append(root)
        while st:
            node = st.pop()
            if node != None:
                if node.right: #添加右节点(空节点不入栈)
                    st.append(node.right)
                
                st.append(node) #添加中节点
                st.append(None) #中节点访问过,但是还没有处理,加入空节点做为标记。
                
                if node.left: #添加左节点(空节点不入栈)
                    st.append(node.left)
            else: #只有遇到空节点的时候,才将下一个节点放进结果集
                node = st.pop() #重新取出栈中元素
                result.append(node.val) #加入到结果集
        return result

后序遍历:

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        result = []
        st = []
        if root:
            st.append(root)
        while st:
            node = st.pop()
            if node != None:
                st.append(node) #中
                st.append(None)
                
                if node.right: #右
                    st.append(node.right)
                if node.left: #左
                    st.append(node.left)
            else:
                node = st.pop()
                result.append(node.val)
        return result

我的代码(当日晚上自己写)

前序遍历:注意处理逻辑,前序为中左右,换成栈的先入后出逻辑为,右左中。

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []

        res = []
        stack = [root]

        while stack :
            node = stack.pop()
            
            if node == None:
                temp = stack.pop()
                res.append(temp.val)
            else :
                if node.right :
                    stack.append(node.right)
                if node.left :
                    stack.append(node.left)
                stack.append(node)
                stack.append(None)
        return res

中序遍历:
左中右,换算成栈为:右中左

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []

        stack = [root]
        res = []

        while stack :
            node = stack.pop()
            if node == None :
                res.append(stack.pop().val)

            else:
                if node.right :
                    stack.append(node.right)
                stack.append(node)
                stack.append(None)
                if node.left :
                    stack.append(node.left)
        return res

后序遍历:
左右中,换算成栈为:中右左

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []
        stack = [root]
        res = []

        while stack :
            node = stack.pop()
            if node == None :
                res.append(stack.pop().val)
            else :
                stack.append(node)
                stack.append(None)
                if node.right :
                    stack.append(node.right)

                if node.left :
                    stack.append(node.left)

        return res

二叉树的层序遍历

没思路,直接对代码随想录的文章进行学习。需要明确:层序遍历用队列来实现。

重点

迭代法的理解较为直观。递归方法的理解:按照递归三部曲,确定处理逻辑即可。

代码随想录的代码

迭代法:

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []
        queue = collections.deque([root])
        result = []
        while queue:
            level = []
            for _ in range(len(queue)):
                cur = queue.popleft()
                level.append(cur.val)
                if cur.left:
                    queue.append(cur.left)
                if cur.right:
                    queue.append(cur.right)
            result.append(level)
        return result

递归法:

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        levels = []
        self.helper(root, 0, levels)
        return levels
    
    def helper(self, node, level, levels):
        if not node:
            return
        if len(levels) == level:
            levels.append([])
        levels[level].append(node.val)
        self.helper(node.left, level + 1, levels)
        self.helper(node.right, level + 1, levels)

我的代码(当日晚上自己写)

迭代法:

from collections import deque
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root == None :
            return []
        result = []
        
        dq = deque()
        dq.append(root)

        while dq :
            size = len(dq)
            res = []
            for i in range(size):
                node = dq.popleft()
                res.append(node.val)
                if node.left:
                    dq.append(node.left)
                if node.right :
                    dq.append(node.right)
            result.append(res)

        return result

递归法:(第一遍错误代码)

如果加嵌套列表以及递归逻辑均错误。

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root == None :
            return []
        res = []
        self.digui(root,0,res)
        return res

    def digui(self,root,depth,res):
        if root == None:
            return 
        res[depth].append(root.val)
        depth += 1
        self.digui(root.left,depth,res)
        depth -= 1
        self.digui(root.right,depth,res)
        depth -= 1

递归法:(改正后代码)
每次记得append空列表,depth不需要回退减一,因为depth是参数,回退之后自然就是正确的数值。

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root == None :
            return []
        res = []
        self.digui(root,0,res)
        return res

    def digui(self,root,depth,res):
        if root == None:
            return 
        if len(res)==depth:
            res.append([])
        res[depth].append(root.val)
        depth += 1
        self.digui(root.left,depth,res)
        self.digui(root.right,depth,res)

快速过完所有二叉树题目后记录

粗略过完二叉树的所有题目后,也算是对递归和回溯有了大致的认识。

在涉及深度的递归+回溯中,有如下的几种写法,都是等价的:

# 第一种
if left :
	depth += 1
	self.digui(depth)
	depth -= 1
if right :
	depth += 1
	self.digui(depth)
	depth -= 1

# 第二种
if left :
	self.digui(depth+1)	
if right :
	self.digui(depth)

# 第三种 这是因为,在做完 right 后的递归函数后,目前所在的函数不会再对depth做操作了,
# 也不会再将其传入某个函数了,也不会return depth,目前函数要回退了,depth不再需要修正。
if left :
	depth += 1
	self.digui(depth)
	depth -= 1
if right :
	depth += 1
	self.digui(depth)

# 第四种
depth += 1
if left :
	self.digui(depth)
if right :
	self.digui(depth)

弄懂,递归和回溯,的关系,很关键。

用层序遍历打10个!

注:后面10题,均使用迭代法来进行层序遍历,递归版本的二刷再写。

102 就是层序遍历

107 二叉树的层序遍历 II

先获取层序遍历,后翻转。

我的代码

from collections import deque
class Solution:
    def levelOrderBottom(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root == None :
            return []
        dq = deque()
        dq.append(root)
        res = []
        while dq :
            size = len(dq)
            level = []
            for i in range(size):
                node = dq.popleft()
                level.append(node.val)
                if node.left :
                    dq.append(node.left)
                if node.right:
                    dq.append(node.right)
            res.append(level)

        n = len(res)
        left = 0
        right = n-1
        while left < right :
            res[left],res[right] = res[right],res[left]
            left += 1
            right -= 1
        return res


199 二叉树的右视图

难点在于,要想到用层序遍历,一直向right遍历是不行的,可能存在左子树比右子树深的情况。

我的代码

from collections import deque
class Solution:
    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []
        res = []
        dq = deque()
        dq.append(root)
        while dq :
            size = len(dq)
            level = []
            for i in range(size):
                node = dq.popleft()
                '''
                这里不用每个都append进level里,甚至level都不需要声明
                只需要 if i == size-1 , 直接append到res里就好了
                '''
                level.append(node.val)
                if node.left :
                    dq.append(node.left)
                if node.right:
                    dq.append(node.right)
            res.append(level[-1])
        return res

637 二叉树的层平均值

过。

429 N 叉树的层序遍历

我的代码

from collections import deque
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root == None :
            return []
        result = []
        
        dq = deque()
        dq.append(root)

        while dq :
            size = len(dq)
            res = []
            for i in range(size):
                node = dq.popleft()
                res.append(node.val)

                n = len(node.children)
                for j in range(n):
                    dq.append(node.children[j])
            result.append(res)
    

        return result

515 在每个树行中找最大值

我的代码

class Solution:
    def largestValues(self, root: Optional[TreeNode]) -> List[int]:
        if root == None :
            return []
        result = []
        
        dq = deque()
        dq.append(root)

        while dq :
            size = len(dq)
            maximum = -inf
            for i in range(size):
                node = dq.popleft()
                if node.val > maximum :
                    maximum = node.val
                
                if node.left:
                    dq.append(node.left)
                if node.right :
                    dq.append(node.right)
            result.append(maximum)

        return result

116 填充每个节点的下一个右侧节点指针

我的代码

from collections import deque
class Solution:
    def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
        if root == None :
            return None
           
        dq = deque()
        dq.append(root)

        while dq :
            size = len(dq)
            
            for i in range(size):
                
                node = dq.popleft()
                if i < size-1:
                    rnext = dq[0]
                    node.next = rnext

                if node.left:
                    dq.append(node.left)
                if node.right :
                    dq.append(node.right)
           
        return root

代码随想录的代码

看上去更好一些,维护一个prev,不需要每次循环去调用 dq[0]

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return root
        
        queue = collections.deque([root])
        
        while queue:
            level_size = len(queue)
            prev = None
            
            for i in range(level_size):
                node = queue.popleft()
                
                if prev:
                    prev.next = node
                
                prev = node
                
                if node.left:
                    queue.append(node.left)
                
                if node.right:
                    queue.append(node.right)
        
        return root

117 填充每个节点的下一个右侧节点指针 II

和116没有任何区别

104 二叉树的最大深度

层序遍历计数,过。

559 n叉树的最大深度

层序遍历计数,过。

111 二叉树的最小深度

需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点。

我的代码

from collections import deque
class Solution:
    def minDepth(self, root: Optional[TreeNode]) -> int:
        if root == None :
            return 0
        
        dq = deque()
        dq.append(root)
        depth = 0
        min_dep = inf

        while dq :
            size = len(dq)
            depth += 1
            for i in range(size):
                node = dq.popleft()
                if node.left == None and node.right == None :
                    if depth < min_dep  :
                        min_dep = depth
                else :
                    if node.left:
                        dq.append(node.left)
                    if node.right :
                        dq.append(node.right)
            

        return min_dep

226 翻转二叉树

一刷没思路,直接学习了代码随想录的讲解。

注意

注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果

这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!

这里自己画图试一试就可以理解,一刷通过画图已理解,如果硬要套中序遍历,是翻转两次 left 。

那么层序遍历可以不可以呢?依然可以的!只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的!

代码随想录的代码

在这里插入图片描述
递归(前序遍历):

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            return None
        root.left, root.right = root.right, root.left
        self.invertTree(root.left)
        self.invertTree(root.right)
        return root

递归(后序遍历):
略,二刷时编写。

递归法(中序遍历,两次 left):

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            return None
        self.invertTree(root.left)
        root.left, root.right = root.right, root.left
        self.invertTree(root.left)
        return root

迭代法(DFS,前序遍历):

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            return None      
        stack = [root]        
        while stack:
            node = stack.pop()   
            node.left, node.right = node.right, node.left                   
            if node.left:
                stack.append(node.left)
            if node.right:
                stack.append(node.right)  
        return root

迭代法(DFS,后序遍历):
略,二刷时编写。

迭代法(DFS,中序遍历,两次 left):
略,二刷时编写。

迭代法(DFS,统一写法中序遍历,不需要两次 left):
无,注意交换处理逻辑的位置。

迭代法(BFS,层次遍历):

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root: 
            return None

        queue = collections.deque([root])    
        while queue:
            for i in range(len(queue)):
                node = queue.popleft()
                node.left, node.right = node.right, node.left
                if node.left: queue.append(node.left)
                if node.right: queue.append(node.right)
        return root   

我的代码(当日晚上自己写,不是独立完成,二刷要重视)

递归(前序遍历):

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root == None:
            return None
        root.left , root.right = root.right , root.left
        self.invertTree(root.left)
        self.invertTree(root.right)
        return root

递归法(中序遍历,两次 left):

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root == None:
            return None
        self.invertTree(root.left)
        root.left, root.right = root.right, root.left
        self.invertTree(root.left)

        return root

迭代法(DFS,前序遍历):

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root == None :
           return None
        stack = [root]
        while stack:
            node = stack.pop()
            node.left , node.right = node.right , node.left
            if node.right :
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return root

迭代法(DFS,后序遍历):
略,二刷时编写。

迭代法(DFS,中序遍历,两次 left):
略,二刷时编写。

迭代法(DFS,统一写法中序遍历,不需要两次 left):
注意交换处理逻辑的位置。

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root == None :
            return None
        stack = [root]
        
        while stack :
            node = stack.pop()
            if node :
                if node.right:
                    stack.append(node.right)
                stack.append(node)
                stack.append(None)
                
                if node.left:
                    stack.append(node.left)
                
            else :
                node = stack.pop()
                # 注意,交换处理逻辑的位置
                node.left , node.right = node.right , node.left

        return root

迭代法(BFS,层次遍历):

from collections import deque
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root == None :
            return None
        dq = deque()
        dq.append(root)
        
        while dq :
            size = len(dq)
            
            for i in range(size):
                node = dq.popleft()
                # 注意处理逻辑的位置
                node.left, node.right = node.right, node.left
                if node.left :
                    dq.append(node.left)
                if node.right:
                    dq.append(node.right)
      
        return root

二叉树第一周小结

代码随想录总结的很好很到位,直接上链接。

二叉树第一周小结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值