文章目录
- 前言
- 一、预备知识
- 二、解题思路
- 1.DFS
- 226.invert-binary-tree
- 104.maximum-depth-of-binary-tree
- 100.same-tree
- 572.subtree-of-another-tree
- 235.lowest-common-ancestor-of-a-binary-search-tree
- 543.diameter-of-binary-tree
- 110.balanced-binary-tree
- 1448.count-good-nodes-in-binary-tree
- 199.binary-tree-right-side-view
- 98.validate-binary-search-tree
- 230.kth-smallest-element-in-a-bst/description
- 105.construct-binary-tree-from-preorder-and-inorder-traversal
- 124.binary-tree-maximum-path-sum
- 2.BFS
- 3.迭代
前言
整理力扣刷题思路。
- 语言:python
- 题库:来自neetcode: link
一、预备知识
二叉树
二叉树是一种非常基本的数据结构,在计算机科学中经常会用到。想象一棵倒挂的树,二叉树就是从一个顶点开始,向下生长的一棵树。这个顶点我们称之为根节点,它就像是树的起点一样。
每个节点都可以有两个分支,也就是说,每个节点最多可以有两个子节点。这两个子节点分别被称为“左子节点”和“右子节点”。如果节点没有子节点,我们就说这个节点是一个“叶子节点”,因为它在整棵树的最末端。
二叉树的遍历是指按照某种顺序访问树中的每个节点的过程,确保每个节点都被访问一次。具体到前序遍历、中序遍历和后序遍历,它们的区别在于父节点和子节点的访问顺序。
想象一下有这样一棵二叉树:
A
/ \
B C
/ \ \
D E F
这棵树有三层,最顶上的是根节点A,它下面有两个子节点B和C。B节点下面又有两个子节点D和E,而C节点下面有一个子节点F。
前序遍历(Pre-order Traversal)
前序遍历是先访问父节点,然后是左子节点,最后是右子节点。对于上面这棵树的前序遍历结果是:先访问根节点A,然后访问A的左子节点B,接着是B的左子节点D,完成了B的子节点遍历后,我们回到B,然后访问B的右子节点E。完成左侧所有节点后,我们回到根节点A,然后访问A的右子节点C,最后访问C的右子节点F。
因此,前序遍历的结果是:A B D E C F。
中序遍历(In-order Traversal)
中序遍历是先访问左子节点,然后是父节点,最后是右子节点。我们还是以同样的树作为例子,中序遍历先从根节点A开始,但是不是立即访问A,而是先访问它的左子节点B,接着再访问B的左子节点D。因为D没有子节点,我们访问D之后,中序遍历返回到它的父节点B,并访问B。之后是B的右子节点E。遍历完B之后,我们返回到A,并访问A,然后是A的右子节点C,最后是C的右子节点F。
因此,中序遍历的结果是:D B E A C F。
后序遍历(Post-order Traversal)
后序遍历先访问子节点,然后是父节点。如果一个节点有两个子节点,会先访问左子节点,然后是右子节点,最后是父节点。使用同样的示例树来演示,后序遍历开始于根节点A,但我们先下移至左子节点B,然后再下移至B的左子节点D。由于D没有子节点,我们访问D,然后返回到B,但在访问B之前,我们需要访问B的右子节点E。只有访问了E之后,我们才能访问B。之后,我们回到A,并下移至A的右子节点C,同样地,我们先访问C的子节点F,然后才是C。最后访问根节点A。
所以后序遍历的结果是:D E B F C A。
二、解题思路
1.DFS
226.invert-binary-tree
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
link
# 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 invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return root
root.left,root.right = self.invertTree(root.right),self.invertTree(root.left)
return root
104.maximum-depth-of-binary-tree
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
return 1+max(self.maxDepth(root.left), self.maxDepth(root.right))
100.same-tree
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
link
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if not p and not q:
return True
if not p or not q:
return False
return p.val == q.val and self.isSameTree(p.left,q.left) and self.isSameTree(p.right,q.right)
572.subtree-of-another-tree
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
link
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
if not subRoot:
return True
if not root:
return False
return self.sameTree(root,subRoot) or self.isSubtree(root.left,subRoot) or self.isSubtree(root.right,subRoot)
def sameTree(self,p,q):
if not p and not q:
return True
if not p or not q:
return False
return p.val==q.val and self.sameTree(p.left,q.left) and self.sameTree(p.right,q.right)
235.lowest-common-ancestor-of-a-binary-search-tree
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
link
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if p.val<root.val and q.val<root.val:
return self.lowestCommonAncestor(root.left,p,q)
if p.val>root.val and q.val>root.val:
return self.lowestCommonAncestor(root.right,p,q)
return root
参考:link
543.diameter-of-binary-tree
给你一棵二叉树的根节点,返回该树的 直径 。
二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。
两节点之间路径的 长度 由它们之间边数表示。
link
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
self.cnt = 1
def lenTree(root):
if not root:
return 0
l,r = lenTree(root.left),lenTree(root.right)
#cnt保留的是当前最长路径的节点数,所以最后结果要减1
self.cnt = max(self.cnt, l+r+1)
#返回当前根节点往下的某一侧最长路径节点数,+1是+当前根节点
return max(l,r)+1
lenTree(root)
return self.cnt-1
参考:link
110.balanced-binary-tree
给定一个二叉树,判断它是否是 平衡二叉树(平衡二叉树 是指该树所有节点的左右子树的深度相差不超过 1。)
link
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
self.out = True
def depth(root):
if not root:
return 0
l,r = depth(root.left),depth(root.right)
if not -1<=l-r<=1:
self.out = False
#此处的返回值是从当前节点往下的最大深度
return max(l,r)+1
depth(root)
return self.out
这题跟上一题求树的直径做法基本相同,也是每次都要比较左右子树
1448.count-good-nodes-in-binary-tree
给你一棵根为 root 的二叉树,请你返回二叉树中好节点的数目。
「好节点」X 定义为:从根到该节点 X 所经过的节点中,没有任何节点的值大于 X 的值。
link
class Solution:
def goodNodes(self, root: TreeNode, mx=float('-inf')) -> int:
if not root:
return 0
#cnt指示当前节点是否是好节点
cnt = 0
if root.val>=mx:
cnt = 1
mx = root.val
#总的好节点=左子树的+右子树的+当前节点
return self.goodNodes(root.left,mx) + self.goodNodes(root.right,mx) + cnt
199.binary-tree-right-side-view
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
link
class Solution:
def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
ans = []
def dfs(root,depth):
if not root:
return None
#维护深度保证每次只更新当前深度看到的最右侧的节点值
if depth==len(ans):
ans.append(root.val)
#这两个递归保证了将最右侧一直看到底部才会继续向左看
dfs(root.right, depth+1)
dfs(root.left, depth+1)
dfs(root,0)
return ans
98.validate-binary-search-tree
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
link
class Solution:
def isValidBST(self, root: Optional[TreeNode], low=float('-inf'), high=float('inf')) -> bool:
if not root:
return True
#维护每个节点的上下边界
if not low<root.val<high:
return False
return self.isValidBST(root.left,low,root.val) and self.isValidBST(root.right,root.val,high)
230.kth-smallest-element-in-a-bst/description
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
link
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
self.ans = root.val
def dfs(root):
#一直到达最左端的树的底部
#或者计数已经为0,则无需再遍历直接返回
if not root or self.k == 0:
return
dfs(root.left)
#每经过一个节点将计数减1
self.k -= 1
if self.k==0:
self.ans = root.val
dfs(root.right)
self.k = k
dfs(root)
return self.ans
参考:link
105.construct-binary-tree-from-preorder-and-inorder-traversal
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
link
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
hashmap = {}
for idx,order in enumerate(inorder):
hashmap[order] = idx
#pre_idx是当前子树在前序遍历的起始位置
#in_l, in_r是当前子树在中序遍历的起始、结束位置
def dfs(pre_idx, in_l, in_r):
if in_l>in_r:
return
root = TreeNode(preorder[pre_idx])
idx = hashmap[preorder[pre_idx]]
root.left = dfs(pre_idx+1, in_l, idx-1)
root.right = dfs(pre_idx+1+idx-in_l, idx+1, in_r)
return root
return dfs(0,0,len(inorder)-1)
参考:link
124.binary-tree-maximum-path-sum
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
link
class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
self.max = root.val
def dfs(root):
if not root:
return 0
l = max(dfs(root.left),0)
r = max(dfs(root.right),0)
#经过当前节点的最大路径和
cur_max = root.val+l+r
self.max = max(self.max, cur_max)
#函数的返回值是经过当前节点的最大单侧路径和
return max(root.val+l, root.val+r)
dfs(root)
return self.max
2.BFS
226.invert-binary-tree
# 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 invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return root
stack = [root]
while stack:
tmp = stack.pop(0)
#先左右交换,再入栈
#这样每次弹出的第一位都是按翻转后顺序排列的
tmp.left,tmp.right = tmp.right,tmp.left
if tmp.left:
stack.append(tmp.left)
if tmp.right:
stack.append(tmp.right)
return root
104.maximum-depth-of-binary-tree
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
stack = [root]
cnt = 0
while stack:
tmp = []
for node in stack:
if node.left:
tmp.append(node.left)
if node.right:
tmp.append(node.right)
#每进入while一次,就将stack更新为当前深度所有节点
stack = tmp
cnt += 1
return cnt
100.same-tree
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if not p and not q:
return True
if not p or not q:
return False
q1,q2 = [p],[q]
while q1 and q2:
tmp1,tmp2 = q1.pop(0),q2.pop(0)
if tmp1.val != tmp2.val:
return False
l1,r1 = tmp1.left,tmp1.right
l2,r2 = tmp2.left,tmp2.right
if (not l1)^(not l2) or (not r1)^(not r2):
return False
if l1:
q1.append(l1)
if r1:
q1.append(r1)
if l2:
q2.append(l2)
if r2:
q2.append(r2)
return not q1 and not q2
572.subtree-of-another-tree
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
new = [root]
while new:
node = new.pop(0)
if node.val == subRoot.val:
if self.sametree(node, subRoot):
return True
if node.left:
new.append(node.left)
if node.right:
new.append(node.right)
return False
def sameTree(self,p,q):
if not p and not q:
return True
if not p or not q:
return False
return p.val==q.val and self.sameTree(p.left,q.left) and self.sameTree(p.right,q.right)
102.binary-tree-level-order-traversal
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
link
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
ans = []
stack = [root]
while stack:
tmp = []
for _ in range(len(stack)):
node = stack.pop(0)
#tmp储存当前深度所有节点的值
tmp.append(node.val)
#stack加上下一深度的所有节点
#for循环会pop出当前深度的所有节点,结束后刚好只剩下下一深度的节点
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
ans.append(tmp)
return ans
这一题的内容就是做BFS,注意结果返回的是每个节点的值,不是节点
98.validate-binary-search-tree
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
#维护每个节点的上下边界
stack = [(root, float('-inf'), float('inf'))]
while stack:
node,low,high = stack.pop(0)
if not low<node.val<high:
return False
if node.left: stack.append((node.left, low, node.val))
if node.right: stack.append((node.right, node.val, high))
return True
297.serialize-and-deserialize-binary-tree
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root:
return "[]"
stack = [root]
ans = []
while stack:
node = stack.pop(0)
if node:
ans.append(str(node.val))
stack.append(node.left)
stack.append(node.right)
else:
ans.append('null')
return ','.join(ans)
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if data=='[]':
return
val = data.split(',')
root = TreeNode(int(val[0]))
stack,idx = [root],1
while stack:
node = stack.pop(0)
if val[idx] != 'null':
node.left = TreeNode(int(val[idx]))
stack.append(node.left)
idx += 1
if val[idx] != 'null':
node.right = TreeNode(int(val[idx]))
stack.append(node.right)
idx += 1
return root
# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))
参考:link
3.迭代
235.lowest-common-ancestor-of-a-binary-search-tree
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
link
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
while root:
#根据二叉树的特性,向下搜索子树
if p.val<root.val and q.val<root.val:
root = root.left
elif p.val>root.val and q.val>root.val:
root = root.right
else:
return root