本文正在参与掘金团队号上线活动,点击 查看大厂春招职位
面试中写算法是必备环节。算法题的一个特点就是如果做过了,有思路的话,很快就能够做出来;如果没做过,就很考验临场发挥了。我在面试时就有时因为紧张,看到算法题发怵,最后影响面试结果😓。
所以我们在平时要注意加强算法方面的训练,在做算法题的过程中沉淀出自己的解题思路,将问题进行归类和转化,做到一通百通。
分享下学习算法的经验
这里我分享一下我在刷算法题过程中总结的经验:
1、养成好的学习习惯
- 遇到难点,反复思考理解
- 三分理解,七分练习
2、大道至简,思考本质,找内在的重复性
要注意总结归纳,之后看到类似的题目就能联想到对应的解法。比如:
- 遇到树的问题,就可以想到树的深度优先遍历,广度优先遍历,而深度优先遍历想到的是递归,广度优先遍历则是循环+存数组
- 遇到链表可以想到的解题思路有遍历+标记
- 遇到回文,翻转数组,翻转数字想到双指针+交换
- ... 还有很多场景,多多总结
3、摒弃旧习惯
- 不要死磕(5分钟做不出来就看题解吧,充分理解是关键)
- 不要只做一遍,一道题刷五遍(五毒神掌)
- 不懒于看高手代码
只要掌握了算法题的规律,平时多总结积累,以后遇到算法题时,我们就不怕不怕啦~
树相关的算法题
这里我搜集了一波树的算法题,也是为了将同类型的题目放在一起比较,总结内在规律。
整理的题目
分类 | 难度 | 题目 |
---|---|---|
DFS | 简单 | 104. 二叉树的最大深度 |
DFS | 简单 | 111. 二叉树的最小深度 |
BFS | 中等 | 102. 二叉树的层序遍历 |
BFS | 中等 | 107. 二叉树的层序遍历 II |
DFS | 简单 | 226. 翻转二叉树 |
DFS | 简单 | 101. 对称二叉树 |
说明
DFS: 深度优先遍历
BFS:广度优先遍历
开始解题
1. 创建树
做树的算法题要先创建树结构,下文提供了node
方法来创建一个树。
/*
树结构如下
7
3 6
1 2 4 5
*/
function node(val, left = null, right = null) {
return {
val,
left,
right,
}
}
let left = node(3, node(1), node(2))
let right = node(6, node(4), node(5))
let head = node(7, left, right)
2.二叉树的最大深度
力扣链接:104. 二叉树的最大深度
思路分析
树的最大深度涉及到树的遍历,只有遍历完整个树后才能计算出树的高度。而树的遍历又有两种:深度优先遍历和广度优先遍历。下面分别用两种方式实现对树深度的计算。
方法1.深度优先遍历
let maxDepth = function (root) {
if (root == null) {
return 0
} else {
let left = maxDepth(root.left)
let right = maxDepth(root.right)
return Math.max(left, right) + 1
}
}
方法2.广度优先遍历
使用广度优先计算树的深度,实现起来稍微复杂点,注意要点:
-
使用数组保存每个节点
-
使用两个指针,i指向当前索引,last指向当前层最后一项
-
注意层级切换的条件【关键】
if (i == last) { last = arr.length - 1 height++ }
function maxDepth(node) {
if (!node) return 0
let arr = [node],
i = 0,
last = 0,
current,
height = 0
while (i < arr.length) {
current = arr[i]
if (current.left) {
arr.push(current.left)
}
if (current.right) {
arr.push(current.right)
}
if (i == last) {
last = arr.length - 1
height++
}
i++
}
return height
}
复杂度分析
非科班出身的同学往往对时间复杂度,空间复杂度的概念有点懵圈😶,其实时间复杂度就是计算的次数,计算几次就是几。空间复杂度就是在计算过程中用到的额外变量的个数。涉及到树的遍历,一般都是要遍历整个树结构,此时的时间复杂度就是O(n),n是树的节点个数;空间复杂度和使用的栈空间有关,是O(n)。
时间复杂度:O(n)
空间复杂度:O(n)
3. 二叉树的最小深度
力扣链接:111. 二叉树的最小深度
思路分析
此题我就直接用DFS(深度优先)来求解了,大家在做题过程中还可以思考如果用BFS(广度优先)怎么做。因为面试官看你做的顺利,有时会提高难度,把问题引申一下。咱们也该多思考,留后手。
AC代码
let minDepth = function (root) {
if (root == null) {
return 0
} else {
let left = minDepth(root.left)
let right = minDepth(root.right)
return Math.min(left, right) + 1
}
}
4. 广度优先遍历
力扣链接1:102. 二叉树的层序遍历
力扣链接2:107. 二叉树的层序遍历 II
这两道都是广度优先遍历的题目,把这两题放在一起思考,可以加深理解。这种类型的题目还可以进行变种,比如要求返回每层节点的和等。
思路分析
广度优先遍历的核心思想是while循环+数组。创建一个数组,将根节点保存到数组里,再对数组中的每个节点进行遍历,如果有子节点,则添加到数组末尾,直到遍历结束。
AC代码
var levelOrder = function (root) {
if (!root) return []
let resArr = [],
array = [root],
i = 0,
levelLastIndex = 0,
temp = []
while (i < array.length) {
let current = array[i]
if (current.left) {
array.push(current.left)
}
if (current.right) {
array.push(current.right)
}
temp.push(current.val)
if (i == levelLastIndex) {
levelLastIndex = array.length - 1
resArr.push(temp)
temp = []
}
i++
}
return resArr
}
var levelOrderBottom = function (root) {
if (!root) return []
let array = [root],
i = 0,
lastIndex = 0,
temp = [],
res = []
while (i < array.length) {
let current = array[i]
if (current.left) {
array.push(current.left)
}
if (current.right) {
array.push(current.right)
}
temp.push(current.val)
if (i == lastIndex) {
// 在当前层的最后一项
lastIndex = array.length - 1
res.unshift(temp)
temp = []
}
i++
}
return res
}
5. 翻转二叉树
力扣链接:226. 翻转二叉树
思路分析
利用DFS(深度优先)遍历二叉树,将每个节点都进行交换。这里的核心思路是递归+交换
AC代码
var invertTree = function (root) {
if (!root) return root
invertTree(root.left)
invertTree(root.right)
let temp = null
temp = root.left
root.left = root.right
root.right = temp
return root
}
6. 对称二叉树
力扣链接:101. 对称二叉树
思路分析
此题可以用深度优先遍历(递归)的思路求解。
比如看下面这两个子树(他们分别是根节点的左子树和右子树),能观察到这么一个规律:
- 左子树 2的左孩子 == 右子树 2的右孩子;
- 左子树 2 的右孩子 == 右子树 2的左孩子
2 2
/ \ / \
3 4 4 3
/ \ / \ / \ / \
8 7 6 5 5 6 7 8
我们将根节点的左子树记做 left,右子树记做 right。比较 left 是否等于 right,不等的话直接返回就可以了。 如果相等,比较 left 的左节点和 right 的右节点,再比较 left 的右节点和 right 的左节点
根据上面信息可以总结出递归函数的两个条件:
- 终止条件为:left 和 right 不等,或者 left 和 right 都为空
- 递归的比较 left,left 和 right.right,递归比较 left,right 和 right.left
AC代码
function isSymmetric(root) {
return isMirror(root.left, root.right)
function isMirror(node1, node2) {
if (node1 === null && node2 === null) {
return true
}
if (node1 === null || node2 === null) {
return false
}
return (
node1.val === node2.val &&
isMirror(node1.left, node2.right) &&
isMirror(node1.right, node2.left)
)
}
}
总结
遇到树的问题,我们要联想到树的深度优先遍历或广度优先遍历。而深度优先遍历想到的是递归,广度优先遍历则是循环+数组。在面试时可以先把脑海中想到的这些点拿出来套一下,看能不能解,避免出现没有头绪的情况。