初级算法中的树问题
一. 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/xn08xg/
来源:力扣(LeetCode)
解法
解法1—递归
刚开始遇到这道题想着依次对左右结点与父结点的值进行比较,符合条件即可,如下:
# 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 isValidBST(self, root: TreeNode) -> bool:
#走到末尾再返回True, 当有不符合条件时返回False,否则进行递归到末尾
if root is None:
return True
if root.left and root.left.val > root.val:
return False
if root.right and root.right.val < root.val:
return False
return self.isValidBST(root.left) and self.isValidBST(root.right)
但这样进行判断是错误的,只对一个中间结点与左右结点进行比较,并没有考虑二叉搜索树所有左/右子树的值都要小于/大于中间结点的值。遇到[5,4,6,null,null,3,7]这种情况就会判断出错。
所以需要记录每一层树传递下来的值的范围:
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
return self.isValidBST_2(root, -2**32, 2**32)
def isValidBST_2(self, node, minvalue, maxvalue):
if node is None:
return True
if minvalue < node.val < maxvalue:
return self.isValidBST_2(node.left, minvalue, node.val) and self.isValidBST_2(node.right, node.val, maxvalue)
else:
return False
另外一个函数来传递每个结点可处于的范围,若不处于这个范围就返回False。
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),每个结点都会进行一次判断。
- 空间复杂度: O ( n ) O(n) O(n),其中 n n n 为二叉树的节点个数。递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,即二叉树的高度。最坏情况下二叉树为一条链,树的高度为 n n n ,递归最深达到 n n n 层,故最坏情况下空间复杂度为 O ( n ) O(n) O(n)。
解法2—中序遍历迭代
中序遍历顺序:左— 中 —右
当每次遍历的结点值大于上一个遍历的结点值时,符合二叉搜索树的性质。
通过一个栈来存储经过的结点,并用一个变量储存当前结点值,以供与下一个结点值进行比较。
入栈出栈的顺序(先入后出)刚好符合中序遍历结点的顺序。
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
if root is None:
return True
stack = []
minvalue = -2**32
while root or len(stack)>0:
# 遍历到最左侧结点
while root:
# 途径结点入栈
stack.append(root)
root = root.left
root = stack.pop()
if root.val <= minvalue:
return False
minvalue = root.val
root = root.right
return True
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),每个结点都会进行访问。
- 空间复杂度: O ( n ) O(n) O(n),其中 n n n 为二叉树的节点个数,栈最多会存储 n n n个结点。
2.对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/xn7ihv/
来源:力扣(LeetCode)
解法
解法1—递归
另外定义一个函数,负责递归比较左右两个结点的值是否相等。
若相等则继续比较左结点的左结点与右结点的右结点的相等情况,左结点的右结点与右结点的左结点的相等情况。
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if root is None:
return True
else:
return self.symmertric2(root.left, root.right)
def symmertric2(self, root_left, root_right):
if root_left is None and root_right is None:
return True
elif root_left is None or root_right is None:
return False
else:
if root_left.val == root_right.val:
T1 = self.symmertric2(root_left.left, root_right.right)
T2 = self.symmertric2(root_left.right, root_right.left)
if T1 is False or T2 is False:
return False
else:
return True
else:
return False
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),每个结点都会进行访问比较。
- 空间复杂度: O ( n ) O(n) O(n),这里的空间复杂度和递归使用的栈空间有关,这里递归层数不超过 n n n,故渐进空间复杂度为 O ( n ) O(n) O(n)。
解法2—迭代
利用一个队列每次存储两个对称的结点,在队列非空时进行循环。
每次循环比较两个对称点的值,并按照对称顺序将这两个点的左右子结点加入队列中。
当出现不对称情况直接返回False。
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if root is None:
return True
node_list = []
node_list.append(root.left)
node_list.append(root.right)
while len(node_list) > 0:
# 每次两个同时出队,进行比较
left = node_list.pop(0)
right = node_list.pop(0)
if left is None and right is None:
continue
if left is None or right is None or left.val != right.val:
return False
else:
node_list.append(left.left)
node_list.append(right.right)
node_list.append(left.right)
node_list.append(right.left)
return True
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),每个结点都会进行访问比较。
- 空间复杂度: O ( n ) O(n) O(n),将结点需要存储起来,并进行进出操作,队列中最多不超过n个点。
3. 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/xnldjj/
来源:力扣(LeetCode)
解法
广度优先搜索
用一个列表nodelist存储结点,根据当前一层的数量将结点值加入列表r1,并会添加下一层的结点进入nodelist,但是因为for循环范围只有当前一层所以不会对下一层结点进行操作。
当前一层结点值加入一个小列表r1,计算完毕后加入最终的列表result_list。
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if root is None:
return []
node_list = []
result_list = []
node_list.append(root)
while len(node_list) > 0:
n = len(node_list)
r1 = []
# 只对前n个进行操作,因为当前一层结点数只有n个
for i in range(n):
curnode = node_list.pop(0)
r1.append(curnode.val)
if curnode.left:
node_list.append(curnode.left)
if curnode.right:
node_list.append(curnode.right)
result_list.append(r1)
return result_list
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),每个结点都会进行访问操作。
- 空间复杂度: O ( n ) O(n) O(n),需要依次将所有结点的值存入列表。
4.将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/xninbt/
来源:力扣(LeetCode)
解法
递归
因为要生成平衡的二叉搜索树,所以左右侧结点数要尽可能一致,因为是有序数组,所以选择数组中间的值作为根节点,左侧小于该值的数组作为左子树,右侧大于的作为右子树。
因为二叉搜索树中间结点的值位于左右结点之间,所以每次选择数组中间位置的值作为中间结点,小于的部分作为左子树,大于的部分作为右子树,进行递归操作。
将中间位置的值生成为结点,与左子树和右子树部分的中间结点相连(函数的返回值就是中间结点)。
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
n = len(nums)
if n == 0:
return None
elif n == 1:
return TreeNode(nums[0])
else:
t = n//2
head = TreeNode(nums[t])
head.left = self.sortedArrayToBST(nums[:t])
head.right = self.sortedArrayToBST(nums[t+1:])
return head
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),数组的每个值都生成一个结点并连接。
- 空间复杂度: O ( log n ) O(\log n) O(logn),其中 n n n 是数组的长度。空间复杂度不考虑返回值,因此空间复杂度主要取决于递归栈的深度,递归栈的深度是 O ( log n ) O(\log n) O(logn)。
为什么这么建树一定能保证是「平衡」的呢?这里可以参考「1382.将二叉搜索树变平衡」,这两道题的构造方法完全相同,这种方法是正确的,1382 题解中给出了这个方法的正确性证明:1382官方题解,感兴趣的同学可以戳进去参考。