系列文章目录
代码随想录算法训练营第一天|数组理论基础,704. 二分查找,27. 移除元素
代码随想录算法训练营第二天|977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II
代码随想录算法训练营第三天|链表理论基础,203.移除链表元素,707.设计链表,206.反转链表
代码随想录算法训练营第四天|24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表II,总结
代码随想录算法训练营第五天|哈希表理论基础,242.有效的字母异位词,349. 两个数组的交集,202. 快乐数,1. 两数之和
代码随想录算法训练营第六天|454.四数相加II,383. 赎金信,15. 三数之和,18. 四数之和,总结
代码随想录算法训练营第七天|344.反转字符串,541. 反转字符串II,卡码网:54.替换数字,151.翻转字符串里的单词,卡码网:55.右旋转字符串
代码随想录算法训练营第八天|28. 实现 strStr(),459.重复的子字符串,字符串总结,双指针回顾
代码随想录算法训练营第九天|理论基础,232.用栈实现队列,225. 用队列实现栈
代码随想录算法训练营第十天|20. 有效的括号,1047. 删除字符串中的所有相邻重复项,150. 逆波兰表达式求值
代码随想录算法训练营第十一天|239. 滑动窗口最大值,347.前 K 个高频元素,总结
理论基础
(一)二叉树的题目类别
- 二叉树的遍历方式: 144.二叉树的前序遍历;145.二叉树的后序遍历;94.二叉树的中序遍历;102.二叉树的层序遍历。
- 二叉树的属性: 101.对称二叉树;104.二叉树的最大深度;111.二叉树的最小深度;222.完全二叉树的节点个数;110.平衡二叉树;257.二叉树的所有路径;404.左叶子之和;513.找树左下角的值;112.路径总和。
- 二叉树的修改与构造: 226.翻转二叉树;106.从中序与后序遍历序列构造二叉树;654.最大二叉树;617.合并二叉树。
- 求二叉搜索树的属性: 700.二叉搜索树中的搜索;98.验证二叉搜索树;530.二叉搜索树的最小绝对差;501.二叉搜索树中的众数;538.把二叉搜索树转换为累加树。
- 二叉树公共祖先问题: 236.二叉树的最近公共祖先;235.二叉搜索树的最近公共祖先。
- 二叉搜索树的修改与构造: 701.二叉搜索树中的插入操作;450.删除二叉搜索树中的节点;669.修剪二叉搜索树;108.将有序数组转换为二叉搜索树。
(二)二叉树的种类
-
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。下图即为深度为k,有2^k-1个节点的满二叉树:
-
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
-
二叉搜索树
二叉搜索树与满二叉树、完全二叉树的区分在于二叉搜索书是有数值的,是一个有序树。- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
-
平衡二叉搜索树
平衡二叉搜索树又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
(三)二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。链式存储方式使用指针, 顺序存储的方式使用数组;顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。
链式存储:
顺序存储: 顺序存储就是用数组来存储二叉树,如果父节点的数组下标是
i
i
i,那么它的左孩子就是
i
∗
2
+
1
i * 2 + 1
i∗2+1,右孩子是
i
∗
2
+
2
i * 2 + 2
i∗2+2:
(四)二叉树的遍历方式
二叉树主要有两种遍历方式:
- 深度优先遍历: 先往深走,遇到叶子节点再往回走。(前中后指的就是中间节点的遍历顺序)
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
- 广度优先遍历: 一层一层的去遍历。
- 层次遍历(迭代法)
- 层次遍历(迭代法)
栈其实就是递归的一种实现结构,前中后序遍历的逻辑其实都是可以借助栈使用递归的方式来实现的;而广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
(五)二叉树的定义
链式存储的二叉树节点的定义方式:和链表差不多,相对于链表 ,二叉树的节点里多了一个指针, 有两个指针,指向左右孩子。
class TreeNode:
def __init__(self, val, left = None, right = None):
self.val = val
self.left = left
self.right = right
二叉树的递归遍历
视频讲解:每次写递归都要靠直觉? 这次带你学透二叉树的递归遍历!| LeetCode:144.前序遍历,145.后序遍历,94.中序遍历
递归算法的三要素:
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型;
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出;
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
前序遍历:
题目链接:144.前序遍历
# 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 preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
left=self.preorderTraversal(root.left)
right=self.preorderTraversal(root.right)
return [root.val]+left+right
后序遍历:
题目链接:145.后序遍历
# 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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
left=self.postorderTraversal(root.left)
right=self.postorderTraversal(root.right)
return left+right+[root.val]
中序遍历:
题目链接:94.中序遍历
# 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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
left=self.inorderTraversal(root.left)
right=self.inorderTraversal(root.right)
return left+[root.val]+right
二叉树的迭代遍历
视频讲解:
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
前序遍历:
题目链接:144.前序遍历
# 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 preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
stack=[root]
result=[]
while stack:
node=stack.pop()
#处理中节点
result.append(node.val)
#右节点入栈
if node.right:
stack.append(node.right)
#左节点入栈
if node.left:
stack.append(node.left)
return result
后序遍历:
题目链接:145.后序遍历
# 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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
stack=[root]
result=[]
while stack:
#处理中节点
node=stack.pop()
result.append(node.val)
#处理左节点
if node.left:
stack.append(node.left)
#处理右节点
if node.right:
stack.append(node.right)
return result[::-1]
中序遍历:
题目链接:94.中序遍历
# 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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
stack=[]
result=[]
cur=root
while cur or stack:
if cur:
stack.append(cur)
cur=cur.left
else:
cur=stack.pop()
result.append(cur.val)
cur=cur.right
return result
统一迭代
核心思想:将访问的节点放入栈中,把要处理的节点也放入栈中,紧接着放入一个空指针作为标记。这样就使得前序、后序、中序的代码只是顺序上稍有不同。
前序遍历:
题目链接:144.前序遍历
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
后序遍历:
题目链接:145.后序遍历
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
中序遍历:
题目链接:94.中序遍历
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