Leetcode 94. 二叉树的中序遍历
递归解法:
非常经典简单的递归写法,提前新建一个res list来接收中序遍历返回的node的val值,然后写一个深度优先搜索的函数,先call recursive function with root.left, 然后再加入root.val 再call recursive function on root.right
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
# create the result list
res = []
# define the depth first search with in order traversal
def dfs(root):
if not root:
return
dfs(root.left)
res.append(root.val)
dfs(root.right)
dfs(root)
return res
Leetcode 104. 二叉树的最大深度
递归解法:
利用递归解决问题,想象成一个二叉树最大的深度其实就是当前root自己算上一个深度所以 + 1,然后再加上左右子树中拥有最大深度的那个的深度,所以就可以对左右子树进行recursive call来解决问题,然后base case就是当我们已经递归到了树的最低端就是None 的时候,None node是没有深度的(也就是leaf node的左右node),这时候应该return 0,这里就make sense应该结束这层递归以后,在leaf node那一层刚好可以获得maxdepth of 1 from 1 + max(0, 0) = 1
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
# return depth of 0 when we find a leaf node
if not root:
return 0
# the max depth should always be root node's depth 1 plus the greater depth out of
# left tree and right tree
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
Leetcode 226. 翻转二叉树
递归解法:
这道题的话我们继续用一个递归的方法来解决,对于翻转的操作,其实就是在遍历到每一个tree node的时候,我们把他的左右子树node 调换位置,所以一个普通的dfs遍历就能解决问题,唯一需要注意的一个点就是这里我们遍历树的顺序并不是随意的,前序和后序遍历都没问题,但是不可以中序遍历,因为中序遍历的话,我们会先递归完成左子树的翻转,然后呢我们利用root来翻转左右子树,这个时候翻转过了的左子树变到了右边,接着我们再递归call function to 右子树,可是这时候的右子树已经变成是左子树了,也就是说使用中序遍历解决问题的话,会导致我们没办法对右子树进行更改
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return None
# do the swap
temp = root.left
root.left = root.right
root.right = temp
# move on to the left and right subtree
self.invertTree(root.left)
self.invertTree(root.right)
return root
时间复杂度 O(n): 遍历了每一个tree node
空间复杂度 O(height): 每次有一层新的height就需要进行递归,递归存放function利用栈,会导致额外的空间
Leetcode 101. 对称二叉树
递归解法:
这道题的思路就是对于检查二叉树是否对称,我们每一次只检查一个tree node是不可以的,如果还是用常规的dfs遍历,当我们检查当前的一个node的时候,我们并没有access去查看他对称的另一半的node是否和他一样,所以这里我们不能使用原题给我们的function which 只有一个param root,我们需要自己写一个function然后呢接收两个node
接下来的思路就是当我们可以同时查看左右子树的时候,如何来分辨他们是否对称呢?首先要做的一定是检查这两个node是否都存在或者都是none,这个就是我们的base case,相当于当有none出现的时候,我们已经遍历到了leaf node,这是,如果都是none没有关系,还是对称的,返回True就可以,可是我们不允许出现一个是none一个不是的情况,那就要返回False,如果都不是none,那就继续递归遍历
递归的思路中,我们要确定左右子树是不是对称,首先左右这两个node的val需要一样,其次呢就是他们自己分别的左右子树也要满足对应的node都是对称的,也就是说左node的左子树要和右子树的右node对称,左子树的右node要和右子树的左node对称,满足这三个条件才能返回True
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
# define a dfs function which takes two tree node to traverse
def dfs(left, right):
# base case when left and right are none
if not left and not right:
return True
elif not left or not right:
return False
result = (left.val == right.val) and (dfs(left.left, right.right)) and \
(dfs(left.right, right.left))
return result
return dfs(root.left, root.right)
时间复杂度 O(n): 遍历了每一个tree node
空间复杂度 O(height): 每次有一层新的height就需要进行递归,递归存放function利用栈,会导致额外的空间
Leetcode 543. 二叉树的直径
递归解法:
这道题就像是二叉树最大深度的一种变形,我们需要理解在我们找到整个数的最长直径之前,就和每一个用递归来解决的的二叉树问题一样,我们先把问题缩小,先关注如何找到一个经过当前node的最长直径,我们会发现在任意一个tree node,经过他的最长直径其实就是他左子树的最大深度加他右子树的最大深度,明白了这个道理之后,可能你会单纯想,那我们直接就找到root.left 的最大深度和root.right的最大深度就可以了,但是还有一个重要的问题,题目中也给我们提示了,在整个树中,最长的一条直径路线,不一定要经过树的最顶端的root也就是传入的root
就如我这张图中所示的一样,我们最长的直径的路线其实是经过我标记的那个node的路线,是他的左右子树的最大深度和,所以我们需要在每一次遍历中,都计算一遍当前node的左右子树深度和任何来和最后的res value作比较,这样才能make sure我们永远记录的是最长的直径
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
# create the variable to keep track longest diameter
res = 0
# dfs the tree and always check the sum of left depth and right depth
def dfs(root):
nonlocal res
if not root:
return 0
left = dfs(root.left)
right = dfs(root.right)
res = max(res, left + right)
return 1 + max(left, right)
dfs(root)
return res
时间复杂度 O(n): 遍历了每一个tree node
空间复杂度 O(height): 每次有一层新的height就需要进行递归,递归存放function利用栈,会导致额外的空间
Leetcode 102. 二叉树的层序遍历
迭代队列解法:
这道题要的其实就是二叉树的广度优先遍历,在我们查找玩一层的所有节点之前不去向下找,唯一一点点区别是如果我们单纯需要遍历所有节点的话就直接返回一个list of all the node 就行了,但是这道题需要我们再标明哪些node是在同一层,每一层需要一个单独的sublist
至于二叉树的BFS,我们可以用一个队列queue来解决,我们先把最初的顶部的root节点存放到queue里面,然后呢当我们需要把队列中的节点加入到我们真正的返回的list中去的时候,我们就把当前queue中的数值都pop出来,同时每当我们pop一个node的时候,我们会重新把这个node的左右子节点重新加回到我们的queue中去
如果树是上图的这个例子的话,我们的queue中所含的元素的顺序是这样的
[3] (一开始存进去的root) -> [0] (pop一次,就是queue中元素数量) -> [9, 20] (3的left和right node被加入进去) -> 这时会连续pop两次因为我们到了新的一层并且queue中两个元素[20, 7] (9被pop掉然后7被加入) -> [7, 15, 7](20被pop掉然后加入15和7) 这时一层又结束了,然后再到下一层然后把queue中的三个元素都pop掉加入return的list
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
# create the res list and queue
res = []
q = deque()
q.append(root)
# while we have element in the queue, keep search
while q:
# create the sublist for each level
level = []
# check if the node in the current q, if it is a node, add its value to
# the sublist and add its left and right node to the queue to
# get ready for the next level search
for _ in range(len(q)):
node = q.popleft()
if node:
level.append(node.val)
q.append(node.left)
q.append(node.right)
if level:
res.append(level)
return res
时间复杂度 O(n): 遍历了每一个tree node
空间复杂度 O(n): 我们一层的node数量最多最多是n/2 的数量,也是我们的queue最多需要存放的元素数量,所以2/n 简化成O(n)
Leetcode 108. 有序数组变二叉搜索树
递归解法:
这道题的关键是我们需要把有序的数组变成一个二叉搜索树BST,BST树一个很关键的点就是需要是balanced,否则的话,对于任何有序的数组我们都可以单纯的遍历然后一直把更大的数值加到每个节点的右节点就好了
这里为了能够达到左右平衡,就要求我们的root节点所在的位置必须在数组的中间位置,只有小于他和大于他的节点数量一样或最大差一,才能保证树是balanced的,然后呢因为整个树都要保证是一个二叉搜索树,所以无论我们找到树中的任意一个节点,他都满足左子树和右子树中的节点数量一样或者相差1并且左边比root小,右边比root大,那么我们就找到了递归的思路
我们利用一个helper method,然后呢他可以帮我们记录当前数组的起始和结尾在哪,然后呢我们永远选择当前数组中间的元素作为root节点,然后把mid左边的元素安排成为他的左子树,右边的元素安排到他的右子树,在递归中,我们就对左右子树再进行一次分割,update新的左右指针位置,然后选择新的mid,直到我们分到最左边或者最右边的节点后再次更新的时候左节点会变到有节点的右边,这就是我们的base case,这里就需要return None因为已经找到了作为leaf node的值了
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
def helper(ls, left, right):
if left > right:
return None
mid = (left + right) // 2
new_node = TreeNode(ls[mid])
new_node.left = helper(ls, left, mid - 1)
new_node.right = helper(ls, mid + 1, right)
return new_node
return helper(nums, 0, len(nums) - 1)
时间复杂度 O(n): 数组中每一个数字都访问了一遍
空间复杂度 O(log n): 每一次递归都把数组分成两半
Leetcode 98. 验证二叉搜索树
递归解法:
这道题在我自己一开始做的时候想的太简单了,最初的想法就是dfs遍历整个树然后对于每一个node都check一下是否左node比root小,右node比root大,代码写起来也很简单,大概是这样的
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
compare = True
if root.left and root.left.val >= root.val:
compare = False
if root.right and root.right.val <= root.val:
compare = False
return compare and self.isValidBST(root.left) and self.isValidBST(root.right)
可是提交过后很快发现了这种写法的问题,只保证每一个node是不行的,BST要求的是左右子树中包含的所有元素都需要小于和大于root,所以例如下面的这种情况就不可取
所以呢新的解法更换了思路,与其让root和自己的左右node作比较,我们从顶端开始就应该记录每一个子节点能够保证是BST的范围,如图所示,一开始顶端的root无所谓,所以他的范围是正无穷到负无穷,然后呢到了左边的node,因为左边的node需要比root小,所以他的范围其实就是在 (-inf, 5)之间,然后呢3 是符合的,所以没关系,然后再继续看右边,右节点的范围应该是(5, inf), 然后7是满足的,因为7还有子节点所以继续遍历,这时候7的左边范围应该要在(5, 7)之间,因为往左边遍历,只需要更改最大值的范围,这时候4已经违反了,就return false,多看一眼对于右边来说,只需要更改左边的范围所以就变成了(7, inf) 所以8没问题
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
def dfs(root, left, right):
# base case
if not root:
return True
# check if the current root is in the range
if root.val <= left or root.val >= right:
return False
# recursive call for left and right node and change the range
left_check = dfs(root.left, left, root.val)
right_check = dfs(root.right, root.val, right)
return left_check and right_check
return dfs(root, float(-inf), float(inf))
自定义的函数中,left 和 right就代表着当前root能够valid需要保证的range,然后我们就先直接对比,这里要用大于等于和小于等于因为题目中标明了这个BST是不能等于的,然后如果当前的root没问题,我们再进行recursive call,这时对root.left,我们修改右边距,反之修改左边距
Leetcode 230. 二叉搜索树中第K小元素
中序遍历获得有序数组解法:
这道题一个最普通基础的解法就是可以利用二叉搜索树的特性,因为左节点<root<右节点,所以我们可以使用一个深度优先的中序遍历然后把元素一一加入到一个列表中去,这样就能得到树中所有元素从小到大的一个排序,然后呢只需要返回list中的k-1 index中存放的元素就可以了
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
num = []
def dfs(root):
nonlocal num
if not root:
return
dfs(root.left)
num.append(root.val)
dfs(root.right)
dfs(root)
return num[k - 1]
递归中序遍历同时检查k的值解法:
然后我们就会去想,既然我们遍历的时候就是从小到大查看每一个node的,那没有必要把整个树都遍历完,只需要刚好遍历到 k 个元素的时候就直接返回不就好了。所以这里我们又更新了递归的解法让我们能够更早的确认res的值并且不需要建立一个数组
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
res = None
k = k
def dfs(root):
nonlocal res
nonlocal k
if not root or res:
return
dfs(root.left)
k = k - 1
if k == 0:
res = root.val
dfs(root.right)
dfs(root)
return res
这里的base case就是说出了找到一个空的节点之外,如果res已经被赋值了证明已经找到了最小的第k个数,那么就直接return 就可以了,不需要再继续进行recursive call 了