前言
每轮面试考一两个算法已经成为大厂面试普遍现象,从面试的角度看,性价比比较高的知识体系算法肯定算一个。有人说对于前端来说算法没太大用,有人说可以锻炼思维等等很多好处,很有用。其实我们没太多的必要花费时间去讨论工程能力和算法能力的问题,目前摆在面前的事实就是很多面试官越来越偏向于算法的考察,对于这样的情况为了顺利的通过这样的考察,我还是打算去学一下比较常考的算法。如果没有非常多的时间去面对力扣上的成百上千道题,剑指offer将是一个不错的选择。(以下题目按照牛客网在线编程排序,此文包含前1/3题目,下列代码均已通过牛客网OJ。)
1.二维数组中的查找(Write Code)
题目描述:
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解释:
题目的条件就是说判断二维数组中是否含有该整数。1.我们可以把数组拍平,一层循环遍历。2.也可以直接双层循环暴力解法,时间复杂度O(n^2)。当然上面两种方法都可以通过。3.但是这个题规定了每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。我们从右上角开始,如果一行中最大的值(最后一个)小于目标值,就没必要遍历这行中其他的值啦,这样可以少遍历很多值。
代码实现:
1.数组拍平循环
function Find(target, array) {
// 定义一个flag标志
var flag;
// 将二维数组拍平
while (array.some(item => Array.isArray(item))) {
array.forEach(item => {
array = [].concat(...array)
})
}
// 循环数组,如果找到相等的,赋值给flag
array.forEach(item => {
if (item == target) {
flag = target;
}
})
// 判断数组中是否含有该整数(flag有值的话,代表有)
if (flag) return true;
return false;
}
2.双层循环
function Find(target, array) {
// 定义len1表示二维数组中的一维数组的个数
let len1 = array.length;
// 定义len2表示每个一维数组的长度(长度相同)
let len2 = array[0].length;
// 双层循环
for (var i = 0; i < len1; i++) {
for (var j = 0; j < len2; j++) {
if (array[i][j] === target) {
return true;
}
}
}
return false;
}
3.简单一次循环
function Find(target, array) {
// 定义len1表示二维数组中的一维数组的个数
let len1 = array.length;
// 定义len2表示每个一维数组的长度(长度相同)
let len2 = array[0].length;
// 定义i来控制行,j控制列
let i = 0,j = len2 - 1;
while (i < len1 && j >= 0) {
// 如果相等,返回true
if (array[i][j] == target) {
return true;
} else if (array[i][j] > target) {// 如果大于目标值,沿当前行往前走
j--;
} else {
// 如果小于目标值,说明这一行都小,需要换到下一行
i++;
}
}
return false;
}
2.替换空格(Write Code)
题目描述:
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
解释:
这个第一感觉就是直接用正则表达式将空格替换即可。或者使用str.split(" ").join("%20")。循环遍历也是可以的。
代码实现:
function replaceSpace(str) {
// 正则表达式'\s'匹配空格用%20替换即可
return str.replace(/\s/g, '%20');
}
3.从尾到头打印链表(Write Code)
题目描述:
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
解释:
这个题要求从尾到头打印,可以正常循环链表,将链表的每一个节点从数组的头部压入(unshift)即可。
代码实现:
function printListFromTailToHead(head) {
// 定义指针指向头节点
let cur = head;
// 定义数组用来存放结果
let result = [];
// 循环链表,直到最后一个节点为止
while(cur){
// 从数组的头部压入
result.unshift(cur.val);
// 向下走
cur = cur.next;
}
// 返回结果
return result;
}
4.重建二叉树(Write Code)
题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
解释:
用前序遍历和中序遍历来构建二叉树。我们从前序遍历中可以得知第一个节点为当前二叉树的根节点,在中序遍历中找到当前的根节点,他们的左边的所有节点为左子树,右边的所有节点为右子树,由此一次递归下去。简单梳理一下步骤:
- 在前序遍历中获取根节点
- 在中序遍历中根据刚刚在前序遍历中获取的根节点找到当前二叉树的左子树和右子树
- 递归,依次在左右子树中这样寻找,直至还原整个二叉树
代码实现:
function reConstructBinaryTree(pre, vin) {
// 当有一个长度为0时,返回null
if (!pre.length || !vin.length) {
return null;
}
// 根据前序遍历遍历的第一个节点为跟节点,创造二叉树
const rootVal = pre[0];
const node = new TreeNode(rootVal);
let i; // 定义i,计算出根节点在中序遍历结果中的下标
for (i = 0; i < vin.length; i++) {
// 找出在中序遍历结果中的下标
if (vin[i] === rootVal) {
break;
}
}
// 以下标为分割线,把前序遍历和中序遍历的结果进行分割。按照这样递归下去。例如:
// 1
// 2 3
// 4 5
// 前序:1 2 4 5 3
// 中序:4 2 5 1 3
// [2,4,5]为左节点,[3]为右节点(以i为分割线进行分割的)
node.left = reConstructBinaryTree(pre.slice(1, i + 1), vin.slice(0, i));
node.right = reConstructBinaryTree(pre.slice(i + 1), vin.slice(i + 1));
// 返回结果
return node;
}
5.用两个栈实现队列(Write Code)
题目描述:
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
解释:
队列和栈二者都可以用数组模拟,区别就是栈是先进后出,队列是先进先出。我们定义两个栈stack1和stack2,在stack1中正常push元素,当从数组中出元素的时候需要用stack2栈来辅助:将stack1中的元素弹出,push进stack2中,在将元素从stack2中弹出就可以实现队列出的操作。要坚持,出队操作只能依赖 stack2 来完成。
代码实现:
//定义两个栈
var stack1 = [],
stack2 = [];
//push的话,正常push
function push(node) {
stack1.push(node);
}
//模拟队列的pop,是需要pop出栈的最底部元素,
//所以把stack1中的队列先pop出来,在push进stack2队列,就可以颠倒过来啦
function pop() {
// 当stack2里面没有值的时候
if (stack2.length <= 0) {
//当stack1里面有值的时候,就循环
while (stack1.length !== 0) {
//将stack1中的值pop出来,push进stack2中
stack2.push(stack1.pop());
}
}
//模拟队列的pop出来的值,都要从stack2中pop出来
return stack2.pop();
}
6.旋转数组的最小数字(Write Code)
题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
解释:
拿到这个题我的第一个想法是直接sort从小到大排序,然后取出第一项就是最小值,这种方法确实也通过啦,但是可能稍微有些偏离题意。题中显示本题知识点:二分。于是就用二分法从新实现了一下。
题中输入一个非递减排序的数组的“一个旋转”。例如:非递减数组[2,3,5,6,7]。旋转完 [3,5,6,7,2],[5,6,7,2,3],[6,7,2,3,5],[7,2,3,5,6]。这四个都可以当作本题的输入。我们发现:旋转后的数组可以划分为两个有序的子区间,前面区间的元素都大于等于后面的元素,我们要找的是两个子区间的分界。前面一定比后面的大,所以我们用二分法看看中间值是在大的部分,还是在小的部分,如果在大的部分就抛弃掉中间值左边的,在小的部分就抛弃掉中间值右边的,抛弃掉的这部分一定大于等于最小值,不符合所以舍去,看代码。
代码实现:
function minNumberInRotateArray(rotateArray) {
// 定义数组的左右边界
let left = 0,right = rotateArray.length - 1;
while (left < right) {
// 为了防止溢出问题,我们用left+(right-left)/2来取出中间值
const mid = Math.floor(left + (right - left) / 2);
// 如果中间值大于最右边的,说明中间值在大的区间部分,舍去中间值左边的
if (rotateArray[mid] > rotateArray[right]) {
left = mid + 1;
} // 如果中间值小于最右边的,说明中间值在小的区间部分,舍去中间值右边的
else if (rotateArray[mid] < rotateArray[right]) {
right = mid;
} // 如果相等,就right--或者left++,从新更新区间
else {
right--;
}
}
// 此时left就是最小值
return rotateArray[left];
}
7.斐波那契数列(Write Code)
题目描述:
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。n<=39
解释:
题中要求求出第n项的值,我们可以直接循环,第n项 = 第n-1项+第n-2项,每次循环不断更新前面项的值即可求出。
代码实现:
function Fibonacci(n)
{
// 如果n = 0,直接返回
if (n == 0) {
return 0;
}
var pre = 0; // 定义前一个值
var cur = 1; // 定义当前值
// 从i=2后开始
for (var i = 2; i <= n; i++) {
// 计算当前值 = 前两项相加
cur += pre;
// 更新pre,始终保持它为当前值的前一个值
pre = cur - pre;
}
// 返回第n项当前值
return cur;
}
8.跳台阶(Write Code)
题目描述:
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
解释:
青蛙跳台阶这种经典题,和斐波那契数列一样,一般可以使用递归来解答。但是递归的解法可能会爆栈,所以为了以防爆栈,可以定义cache进行缓存。(也可以用循环来代替递归的方式正向求解)
代码实现:
// 为了以防爆栈,定义cache进行缓存
let cache = {}
function jumpFloor(number) {
// 当为1或者2时,直接返回number(递归出口)
if (number === 1 || number === 2) {
return number;
}
// 如果已经计算过了,直接从缓存中取出返回即可
if (cache[number]) {
return cache[number]
}
// 递归调用,进行缓存
cache[number] = jumpFloor(number - 1) + jumpFloor(number - 2);
// 返回当前结果
return cache[number];
}
9.变态跳台阶(Write Code)
题目描述:
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解释:
变态跳台阶和跳台阶这两个问题本质上是一个问题。区别在于跳台阶问题的第10层的算法为第9层+第8层,而变态跳台阶的第10层的算法为第9层+第8层+第7层+ ··· +第1层+1。我们简单写一下是这样的:1 2 4 8 16 32 64 …
代码实现:
function jumpFloorII(number)
{
if (number == 1) {
return 1;
}
return jumpFloorII(number - 1) * 2;
}
10.矩阵覆盖(Write Code)
题目描述:
我们可以用2 * 1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2 * 1的小矩形无重叠地覆盖一个2 * n的大矩形,总共有多少种方法?
比如n=3时,2*3的矩形块有3种覆盖方法。
解释:
这个题还是斐波那契数列的一个变形。当我们第一次看见这个题无从下手的时候,感觉还是找好规律最重要。拿2 * n的大矩形,和n个2 * 1的小矩形来举例。当n小于2的时候只有n中摆放方法。当n>2的时候,可以看出等于前两次的相加。
代码实现:
function rectCover(number) {
// 计算特殊情况
if(number == 0 || number == 1 || number == 2) return number;
// 定义最初始的值
let m = 1;
let n = 2;
// 一直循环,更新n和m
while (--number){
n = m + n;
m = n - m;
}
// 返回结果
return m;
}
11.二进制中1的个数(Write Code)
题目描述:
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。
例如:输入:10 输出:2
解释:
与、或和异或的运算规律:
| 与(&) | 或(|) | 异或(^) |
|---|---|---|
| 0 & 0 = 0 | 0 | 0 = 0 | 0 ^ 0 = 0 |
| 0 & 1 = 0 | 0 | 1 = 1 | 1 ^ 0 = 1 |
| 1 & 0 = 0 | 1 | 0 = 1 | 0 ^ 1 = 1 |
| 1 & 1 = 1 | 1 | 1 = 1 | 1 ^ 1 = 0 |
比如n = 10的二进制是1010,n-1 = 9的二进制为1001,n&(n-1)= 1000。 可以看出n的最低位的1被与没啦。依次n&(n-1)的往下走,每次都可以与没最低位的1。所以,要消除整数n最低位的1,可以使用 n = n & (n-1),直至为0。
代码实现:
function NumberOf1(n)
{
// 定义一个count用于计数
let count = 0;
// 当n不为0的时候,一直循环
while(n!=0){
// count+1
count++
// 开始与没最低位的1
n = n&(n-1);
}
// 返回次数
return count
}
12.数值的整数次方,直接Math.pow(base, exponent)或者(Write Code)
题目描述:
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0
解释:
数值的整数次方,直接Math.pow(base, exponent)或者base** exponent即可。
代码实现:
function Power(base, exponent)
{
// write code here
return base**exponent;
}
13.调整数组顺序使奇数位于偶数前面(Write Code)
题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解释:
按照题目要求可以直接一次循环,将奇数和偶数分开放进不同的数组中,最后在进行concat拼接即可。
代码实现:
function reOrderArray(array) {
//定义result1数组用于存放奇数
let result1 = [];
//定义result2数组用于存放偶数
let result2 = [];
//缓存长度
let len = array.length;
for (let i = 0; i < len; i++) {
//判断如果是奇数存进result1
if (array[i] % 2 === 1) {
result1.push(array[i]);
} else {
// 奇数存进result2
result2.push(array[i]);
}
}
//拼接奇数+偶数
return result1.concat(result2);
}
14.链表中倒数第k个结点(Write Code)
题目描述:
输入一个链表,输出该链表中倒数第k个结点。
解释:
用快慢指针来求解。定义两个指针fast和solw,先让fast指针向前走K步,然后快慢指针一起走,直到fast指针走到最后一个节点,这个时候slow指针的后继节点就是这个倒数第K个节点,删除即可。
代码实现:
function FindKthToTail(head, k) {
// 判断链表为空的情况
if (!head) return null;
//设置第一个结点的前驱结点,保证所有的结点都能有一个前驱结点
let dummy = new ListNode(0);
dummy.next = head
//定义两个指针,快慢指针
let fast = dummy;
let slow = dummy;
//让快指针先走k步
while (k !== 0 && fast != null) {
fast = fast.next;
k--;
}
//如果k>0,说明k太大,倒数第k个节点不存在
if (k > 0 || !fast) return null;
//快慢指针一起走,直到fast指针走到最后一个节点
while (fast.next) {
fast = fast.next;
slow = slow.next;
}
//此时慢指针的后继节点就是倒数第k个节点
return slow.next;
}
15.反转链表(Write Code)
题目描述:
输入一个链表,反转链表后,输出新链表的表头。
解释:
反转链表就是在操作链表的顺序,就是改变节点之间指针的关系。定义指针:pre(前驱)、cur(当前节点)、next(后继),我们改变当前节点的next指针,指向前驱节点(cur.next = pre),所有指针的都这样改变,一步一步往下走即可。
代码实现:
function ReverseList(pHead) {
// 定义一个节点为null,用作前驱节点
let pre = null;
//定义指针,指向当前节点
let cur = pHead;
//循环链表
while (cur) {
//定义next,存储当前节点的后继节点
let next = cur.next;
//当前指针的下一个节点指向前驱节点(反转)
cur.next = pre;
//往下走,pre走到当前节点
pre = cur;
//往下走,当前节点走到上面存储的后继节点
cur = next;
}
//最后pre节点指向原链表的最后一个节点
return pre;
}
16.合并两个排序的链表(Write Code)
题目描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解释:
我们定义一个指针头节点,然后依次比较两个链表当前节点值的大小,将较小的“穿”到新定义的这个指针中,一直往下走,直到都走完,返回新链表起始节点。
代码实现:
function Merge(pHead1, pHead2) {
// 定义头结点,确保链表可以被访问到
let head = new ListNode(0);
// 定义指针,指向头节点
let cur = head;
// 当两个链表都存在时进行循环
while (pHead1 && pHead2) {
// 如果pHead1的值较小
if (pHead1.val < pHead2.val) {
// 将pHead1的值作为下一个节点
cur.next = pHead1;
// pHead2链表向下走
pHead1 = pHead1.next;
} else {
// 当pHead2值较小时,将pHead2的值作为下一个节点
cur.next = pHead2;
// pHead2链表向下走
pHead2 = pHead2.next;
}
// 每循环一次,指针往下走一个节点
cur = cur.next;
}
// 处理链表不等长的情况,哪个链表还有值就放在新链表的后面
cur.next = (pHead1 !== null ? pHead1 : pHead2);
// 返回新链表起始节点
return head.next;
}
17.树的子结构(Write Code)
题目描述:
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
解释:
判断B是不是A的子结构,首先B的头节点,要和A中的某个节点相等,且这个节点的左子树和右子树也要和B的都保持一样,我们用递归来进行实现。
代码实现:
function HasSubtree(pRoot1, pRoot2) {
//如果有一个树为空的话,返回false
if (!pRoot1 || !pRoot2) {
return false;
}
//定义函数判断是否为子结构
function same(root1, root2) {
//先判断root2再判断root1,防止都为空的时候(递归的出口)
//如果root2为空了说明是root1的子结构
if (!root2) return true;
//如果root1为空了说明不对,返回false
if (!root1) return false;
//判断当前两个节点是否相等,递归当前节点的左节点,递归当前节点的右节点
return root1.val == root2.val && same(root1.left, root2.left) && same(root1.right, root2.right)
}
//将传入的树传入same函数进行对比 || “大树”的左节点和小树对比 || “大树”的右节点和小树对比 (有一个OK即可)
return same(pRoot1, pRoot2) || HasSubtree(pRoot1.left, pRoot2) || HasSubtree(pRoot1.right, pRoot2)
}
18.二叉树的镜像(Write Code)
题目描述:
操作给定的二叉树,将其变换为源二叉树的镜像。
解释:
二叉树的镜像实际为反转二叉树。将每一个二叉树的左右子树都发生了互换。用递归的方式遍历树的每一个节点,互换左右子树。
代码实现:
function Mirror(root) {
//递归的出口,如果不存在了,到底了,返回null
if (!root) {
return
}
//递归交换左孩子的子结点
let left = Mirror(root.left);
//递归交换右孩子的子结点
let right = Mirror(root.right);
//交换当前遍历的两个节点
root.left = right;
root.right = left;
return root;
}
19.顺时针打印矩阵(Write Code)
题目描述:
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
解释:
因为是矩阵,我们取到当前矩形左上角坐标和右下角坐标,然后从大圈到小圈一层一层的循环。(这里循环时要注意一下边界的处理情况)
代码实现:
function printMatrix(matrix) {
// 空值校验
if (!matrix.length) return []
if (!matrix[0].length) return []
let height = matrix.length;
let width = matrix[0].length;
let result = [];
// 当前矩形左上角坐标(startX,startY),右下角坐标(endX,endY)
let startX = 0,
startY = 0,
endX = width - 1,
endY = height - 1;
// 一层一层的循环,从大圈到小圈
while (endX >= startX && endY >= startY) {
for (let i = startX; i <= endX; i++) {
result.push(matrix[startY][i]);
}
for (let j = startY + 1; j <= endY; j++) {
result.push(matrix[j][endX]);
}
for (let i = endX - 1; i >= startX; i--) {
if (endY === startY) return result
result.push(matrix[endY][i]);
}
for (let j = endY - 1; j >= startY + 1; j--) {
if (startX === endX) return result
result.push(matrix[j][startX]);
}
startX++;
startY++;
endX--;
endY--;
}
return result;
}
20.包含min函数的栈(Write Code)
题目描述:
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
解释:
题中说,找到栈中所含的最小元素,正常用一个栈来实现的话需要循环一遍找出最小值,时间复杂度为o(n),但是由于要求时间复杂度应为O(1),所以需要借用两个栈来实现。stack1是正常维持的栈,stack2作为它的辅助栈,我们维持stack2的最后一个节点是栈中的最小值,也就是维持他是一个递减的栈,具体实现如下。
代码实现:
// 定义两个栈
var stack1 = [],stack2 = [];
function push(node) {
//把值push进stack1中
stack1.push(node);
//如果stack2为空或者此时节点小于stack2最小值(也就是最后一个节点)把它push进stack2
//我们维持stack2的最后一个节点是最小值,也就是一个递减的栈。
if (stack2.length === 0 || node < stack2[stack2.length - 1]) {
stack2.push(node);
}
}
function pop() {
//在stack1中直接pop出去。如果pop的这个值,恰好是stack2中的最小值,那么stack2也需要pop出去。
if (stack1.pop() === stack2[stack2.length - 1]) {
stack2.pop();
}
}
function top() {
//取出stack1中的最后一个值即可
return stack1[stack1.length - 1];
}
function min() {
//因为我们维持的stack2是一个递减的栈,且最后一个值就是最小值所以取出即可。
return stack2[stack2.length - 1];
}
21.栈的压入、弹出序列(Write Code)
题目描述:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列压到哪一个啦,只要里面有值他就可以弹出。所以定义辅助栈,对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
解释:
判断一个序列是否为一个栈的弹出顺序。这里面的问题就是栈可以在任意时间弹出,不管压栈序列压到哪一个啦,只要里面有值他就可以弹出。所以定义辅助栈,遍历入栈数组,将每个元素依次压入辅助栈,每遍历一个元素,就和出栈数组的第一个元素比较,如果相等则代表这个是目前弹出的元素,将刚刚压入辅助栈的元素弹出,更新出栈数组的第一个元素(往后移一位),继续遍历,最后看辅助栈的长度是否为0。
代码实现:
function IsPopOrder(pushV, popV) {
// 如果pushV或popV为空的话,直接返回
if (!pushV || !popV || pushV.length === 0 || popV.length === 0) {
return
}
// 定义一个辅助栈
let stack = [];
// 定义一个索引,用于在popV中移动
let index = 0;
// 循环栈的压入顺序的数组
for (let i = 0; i < pushV.length; i++) {
// 将值push进辅助栈中
stack.push(pushV[i]);
// 当辅助栈不为空,且最后一个值等于popV的第一个值的时候
while (stack.length !== 0 && stack[stack.length - 1] === popV[index]) {
// 将辅助栈中的最后一个值弹出
stack.pop();
// index+1,在popV中向后移动一个
index++;
}
}
// 如果辅助栈为空,说明是OK的
return stack.length === 0;
}
22.从上往下打印二叉树(Write Code)
题目描述:
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
解释:
二叉树的层序遍历,一般用BFS+队列直接可以解决。(有一些层序遍历的变型题,也是BFS+队列+...来求得的)
代码实现:
//BFS+队列
function PrintFromTopToBottom(root) {
//处理边界,判断为空直接返回
if (!root) return [];
//定义存储结果的数组
let result = [];
//定义一个队列
let queue = [];
//将根节点push进去
queue.push(root);
// 当队列不为空时,反复执行
while (queue.length) {
//缓存长度
let len = queue.length;
for (let i = 0; i < len; i++) {
//将第一个弹出
let cur = queue.shift();
//将当前节点推入结果数组
result.push(cur.val);
//如果有左节点,推到后面
if (cur.left) {
queue.push(cur.left);
}
//如果有右节点,推到后面
if (cur.right) {
queue.push(cur.right);
}
}
}
//返回结果数组
return result;
}
未完待续...