数组
1. 数组中重复的数字
题目
- 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字
解题思路
- 数组排序后,相等的数就会相邻
- 直接循环比较,遇到相等的就返回该值
代码实现
var findRepeatNumber = function(nums) {
nums.sort();
for(var i = 0; i < nums.length-1; i++) {
if(nums[i] == nums[i+1])
return nums[i]
}
};
2. 二维数组中的查找
题目
- 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路
- 数组降维后再次查找
代码实现
var findNumberIn2DArray = function(matrix, target) {
return matrix.flat(Infinity).includes(target)
};
3. 顺时针打印矩阵
题目
- 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
解题思路
- 一层层向里处理,按顺时针依次遍历:上、右、下、左层
- 不再形成“环”了,就会剩下一行或一列,然后单独判断
代码实现
var spiralOrder = function (matrix) {
if (matrix.length === 0) return []
const res = []
let top = 0, bottom = matrix.length - 1, left = 0, right = matrix[0].length - 1
while (top < bottom && left < right) {
for (let i = left; i < right; i++) res.push(matrix[top][i]) // 上层
for (let i = top; i < bottom; i++) res.push(matrix[i][right]) // 右层
for (let i = right; i > left; i--) res.push(matrix[bottom][i])// 下层
for (let i = bottom; i > top; i--) res.push(matrix[i][left]) // 左层
right--
top++
bottom--
left++ // 四个边界同时收缩,进入内层
}
if (top === bottom) // 剩下一行,从左到右依次添加
for (let i = left; i <= right; i++) res.push(matrix[top][i])
else if (left === right) // 剩下一列,从上到下依次添加
for (let i = top; i <= bottom; i++) res.push(matrix[i][left])
return res
};
4. 在排序数组中查找数字I
题目
- 统计一个数字在排序数组中出现的次数
解题思路
- 从数组左侧向右遍历,遇到目标数字 target,停止,记录下标 left
- 从数组右侧向左遍历,遇到目标数字 target,停止,记录下标 right
- 如果 right 小于 left,那么说明没出现,返回 0;否则返回 right - left + 1
代码实现
var search = function(nums, target) {
if (!nums.length) return 0;
let left = 0,
right = nums.length - 1;
while (nums[left] !== target && left < nums.length) {
++left;
}
while (nums[right] !== target && right >= 0) {
--right;
}
return left <= right ? right - left + 1 : 0;
};
5. 0~n-1中缺失的数字
-
题目
- 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
-
解题思路
- 二分查找
- left 指向 0,right 指向最后一个元素
- 计算中间坐标 mid:
- 如果mid = nums[mid],说明[0, mid]范围内不缺失数字,left 更新为 mid + 1
- 如果mid < nums[mid],说明[mid, right]范围内不缺失数字,right 更新为 mid - 1
- 检查 left 是否小于等于 mid,若成立,返回第 2 步;否则,向下执行
- 返回 left 即可
- 二分查找
-
代码实现
var missingNumber = function(nums) {
let left = 0,
right = nums.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (mid === nums[mid]) {
left = mid + 1;
} else if (mid < nums[mid]) {
right = mid - 1;
}
}
return left;
};
栈
1. 用两个栈实现队列
-
题目
- 用两个栈实现一个队列
- 队列的声明如下,请实现它的两个函数
appendTail
和deleteHead
,分别完成在队列尾部插入整数和在队列头部删除整数的功能 - (若队列中没有元素,
deleteHead
操作返回-1
)
-
解题思路
- 利用双栈实现序列倒置
- 假设 stack1=[1, 2, 3] 、 stack2=[] ,循环出栈 stack1 并将出栈元素进栈 stack2 ,循环结束后, stack1=[] 、 stack2=[3, 2, 1] ,即实现元素的倒置
- 当需要删除队首元素时,仅仅需要 stack2 出栈即可
- 当 stack2 为空时,出队就需要将 stack1 元素倒置倒 stack2 , stack2 再出队即可
- 如果 stack1 也为空,即队列中没有元素,返回 -1
- 利用双栈实现序列倒置
-
代码实现
var CQueue = function() {
this.stack1 = [];
this.stack2 = [];
};
CQueue.prototype.appendTail = function(value) {
this.stack1.push(value)
};
CQueue.prototype.deleteHead = function() {
if(this.stack2.length){
return this.stack2.pop()
}
if(!this.stack1.length) return -1
while(this.stack1.length){
this.stack2.push(this.stack1.pop())
}
return this.stack2.pop()
};
2. 包含min函数的栈
题目
- 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的
min
函数在该栈中 - 调用
min
、push
及pop
的时间复杂度都是O(1)
- 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的
解题思路
- 暴力求解
...this.items
是ES6
扩展运算符语法,主要运用于函数的调用
代码实现
var MinStack = function() {
this.items = []
};
MinStack.prototype.push = function(x) {
this.items.push(x)
};
MinStack.prototype.pop = function() {
this.items.pop()
};
MinStack.prototype.top = function() {
return this.items[this.items.length - 1]
};
MinStack.prototype.min = function() {
return Math.min(...this.items)
};
4. 队列的最大值
题目
- 请定义一个队列并实现函数
max_value
得到队列里的最大值 - 要求函数
max_value
、push_back
和pop_front
的均摊时间复杂度都是O(1)
- 若队列为空,
pop_front
和max_value
需要返回-1
- 请定义一个队列并实现函数
解题思路
- 使用两个队列
- 一个队列 queue 用于存放所有元素,另一个辅助队列 dequeue 用来存放当前 queue 中的最大值。
- push 操作:
- 将元素放入 queue 中
- 检查元素是否大于 dequeue 队尾元素
- 如果大于,那么队尾元素出队;直到不再满足大于条件
- pop 操作:
- 如果 queue 的队首元素等于 dequeue 的队首元素,那么 dequeue 队首元素需要出队
- queue 队首元素需要出队
- 使用两个队列
代码实现
var MaxQueue = function() {
this.queue = [];
this.dequeue = [];
};
MaxQueue.prototype.max_value = function() {
return this.dequeue.length ? this.dequeue[0] : -1;
};
MaxQueue.prototype.push_back = function(value) {
this.queue.push(value);
while (
this.dequeue.length &&
value > this.dequeue[this.dequeue.length - 1]
) {
this.dequeue.pop();
}
this.dequeue.push(value);
};
MaxQueue.prototype.pop_front = function() {
if (!this.dequeue.length) {
return -1;
}
if (this.queue[0] === this.dequeue[0]) {
this.dequeue.shift();
}
return this.queue.shift();
};
队列
1. 滑动窗口的最大值
题目
- 给定一个数组
nums
和滑动窗口的大小k
,请找出所有滑动窗口里的最大值
- 给定一个数组
解题思路
- 暴力求解
- 直接移动这个滑动窗口,每次统计窗口中的最大值即可
代码实现
var maxSlidingWindow = function(nums, k) {
if (k <= 1) return nums;
const res = [];
for (let i = 0; i < nums.length - k + 1; ++i) {
res.push(Math.max(...nums.slice(i, i + k)));
}
return res;
};
链表
1. 从尾到头打印链表
题目
- 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)
解题思路
- 使用数组 result 保存结果
- 使用 while 循环遍历链表
- 使用 unshift 方法将每次循环的节点值存到数组中,指向下一个节点
代码实现
var reversePrint = function(head) {
const result = []
while(head !== null) {
result.unshift(head.val)
head = head.next
}
return result
};
2. 删除链表的节点
题目
- 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
解题思路
- 判断要删除的元素是否处于链表的开头
- 如果是开头元素
- 直接返回head.next
- 如果不是开头元素
- 先找到要删除的元素在链表中所在的位置index
- 在利用循环找到要删除的元素current和它的前一个元素previous
- 再让previous.next = current.next即可
- 如果是开头元素
- 判断要删除的元素是否处于链表的开头
代码实现
var deleteNode = function(head, val) {
if(val == head.val){
return head.next;
}
else{
let pre = head;
let index=0;
while(pre){
pre = pre.next;
index++;
if(pre.val == val){
break;
}
};
let previous;
let current = head;
for(let j=0;j<index;j++){
previous = current;
current = current.next;
}
previous.next = current.next;
return head;
}
};
3. 链表中倒数第k个节点
题目
- 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点
解题思路
- 设置一个指针res,初始值指向head
- 用head指针遍历链表,遍历k个结点后(含第一个结点),res跟着往后遍历,当遍历结束时,res即为结果
- 若链表长度小于k,res没有发生变化,返回head
代码实现
var getKthFromEnd = function(head, k) {
let res = head;
while (head.next) {
k--;
if (k <= 0) {
res = res.next;
}
head = head.next;
}
return res;
};
4. 反转链表
题目
- 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点
解题思路
- 利用3个变量在循环过程中记录最后3种信息
- cur游标,一直往后循环,最后会为null
- prev记录前一个节点
- oldNext,变更方向时,需要先用oldNext记住改变前的next节点,否则无法向后循环
代码实现
var reverseList = function(head) {
var prev = null,cur=head,temp;
while(cur){
temp = cur.next;//修改前先记住下一个节点
cur.next = prev; //改别指向,第一个节点prev是null,
prev = cur; //记录前一个节点,供下次循环使用
cur = temp; // cur通过temp指向下一节点
}
return prev;//cur会多循环直到null
};
5. 复杂链表的复制
题目
- 请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null
解题思路
- 创建一个 Map 对象存储创建过的节点
- 在递归函数里创建新的节点,把这个节点加入到 Map 里面去
- 使用递归函数设置设个节点的 next 和 random 指向,返回复制的节点
- 调用递归函数
代码实现
var copyRandomList = function(head) {
const visited = new Map()
function dfs(head) {
if(head === null) return null
if(visited.has(head)) return visited.get(head)
const copy = new Node(head.val)
visited.set(head, copy)
copy.next = dfs(head.next)
copy.random = dfs(head.random)
return copy
}
return dfs(head)
};
6. 两个链表的第一个公共节点
题目
- 输入两个链表,找出它们的第一个公共节点
解题思路
- 开辟哈希表 map。key 是节点,value 是 boolean,代表节点是否出现过
- 对 list1 进行遍历,设置 map[节点]=true
- 对 list2 进行遍历,如果节点在 map 中出现过,那么说明这是两个链表的公共节点,返回
代码实现
var getIntersectionNode = function(headA, headB) {
const map = new Map();
let node = headA;
while (node) {
map.set(node, true);
node = node.next;
}
node = headB;
while (node) {
if (map.has(node)) return node;
node = node.next;
}
return null;
};
哈希表
1. 最长不含重复字符的子字符串
题目
- 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度
解题思路
- 准备 2 个指针 i、j,i 指向窗口左边,j 指向右边
- 指针每次可以向前“滑动”一个位置,它们之间的区域就是“窗口”
整体流程
- 准备哈希表 map。key 是 char,value 是 boolean,代表字符 char 是否出现在滑动窗口内
- i 和 j 初始化为 0,结果 ans 初始化为 0
- 检查s[j]是否出现过:
- 没有出现过,扩大窗口:记录s[j],指针 j 向右滑动一格,更新 ans
- 出现过,缩小窗口:指针 i 向右移动一格,map[s[i]]更新为 false
- 如果 i 和 j 没有越界,回到 step3,否则返回 ans
代码实现
var lengthOfLongestSubstring = function(s) {
const length = s.length;
const map = {}; // char => boolean 代表着char是否在目前的窗口内
let i = 0,
j = 0;
let ans = 0;
while (i < length && j < length) {
if (!map[s[j]]) {
ans = Math.max(j - i + 1, ans);
map[s[j]] = true;
++j;
} else {
// 如果char重复,那么缩小滑动窗口,并更新对应的map
map[s[i]] = false;
++i;
}
}
return ans;
};
2. 第一个只出现一次的字符
题目
- 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母
解题思路
- 使用lastIndexOf和indexOf
- 从前面找和从后面找,找到的下标一致,说明是唯一的
代码实现
var firstUniqChar = function(s) {
if (s == '') return " ";
for (let i = 0 ; i < s.length; i++) {
if (s.lastIndexOf(s[i]) === s.indexOf(s[i])) {
return s[i]
}
}
return " ";
};
树
1. 重建二叉树
题目
- 输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字
解题思路
- 因为前序遍历的第一个元素就是当前二叉树的根节点
- 那么,这个值就可以将中序遍历分成 2 个部分
- 最后,根据左右子树,继续递归即可
代码实现
var buildTree = function(preorder, inorder) {
if (!preorder.length || !inorder.length) {
return null;
}
const rootVal = preorder[0];
const node = new TreeNode(rootVal);
let i = 0; // i有两个含义,一个是根节点在中序遍历结果中的下标,另一个是当前左子树的节点个数
for (; i < inorder.length; ++i) {
if (inorder[i] === rootVal) {
break;
}
}
node.left = buildTree(preorder.slice(1, i + 1), inorder.slice(0, i));
node.right = buildTree(preorder.slice(i + 1), inorder.slice(i + 1));
return node;
};
2. 树的子结构
-
题目
- 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
- B是A的子结构, 即 A中有出现和B相同的结构和节点值
-
解题思路
isSubStructure
的职能:判断 B 是否是 A 的子结构。是,返回 true;否则,尝试 A 的左右子树isSubTree
的职能:封装“判断 B 是否是 A 的子结构”的具体逻辑。
-
代码实现
var isSubStructure = function(A, B) {
// 题目约定:约定空树不是任意一个树的子结构
if (!A || !B) {
return false;
}
return (
isSubTree(A, B) ||
isSubStructure(A.left, B) ||
isSubStructure(A.right, B)
);
};
function isSubTree(pRoot1, pRoot2) {
// B树遍历完了,说明B是A的子结构
if (!pRoot2) {
return true;
}
// A遍历完了,但是B还没有遍历完,那么B肯定不是A的子结构
if (!pRoot1) {
return false;
}
if (pRoot1.val !== pRoot2.val) {
return false;
}
return (
isSubTree(pRoot1.left, pRoot2.left) &&
isSubTree(pRoot1.right, pRoot2.right)
);
}
3. 二叉树的镜像
题目
- 请完成一个函数,输入一个二叉树,该函数输出它的镜像
解题思路
- 从上到下,依次交换每个节点的左右节点
代码实现
var mirrorTree = function(root) {
if (!root) {
return null;
}
// 交换当前节点的左右节点
const leftCopy = root.left;
root.left = root.right;
root.right = leftCopy;
// 对左右子树做相同操作
mirrorTree(root.left);
mirrorTree(root.right);
return root;
};
4. 对称的二叉树
题目
- 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的
解题思路
- 从根节点开始的左右子树分别相等
- 左子树的左节点=右子树的右节点
- 左子树的右节点=右子树的左节点
代码实现
var isSymmetric = function(root) {
if(root==null) return true;
return dfs(root.left,root.right);
function dfs(p,q){
if(!p || !q) return !p&&!q;
if(p.val!==q.val) return false;
return dfs(p.left,q.right) && dfs(p.right,q.left);
}
};
5. 序列化二叉树
-
题目
- 请实现两个函数,分别用来
序列化
和反序列化
二叉树
- 请实现两个函数,分别用来
-
解题思路
- 使用
广度优先(BFS)遍历所有节点(包括空节点)
- 使用
-
序列化流程如下
- 初始化字符串 res
- 初始化队列 queue,将 root 放入队列
- 检查队列是否为空:
- 队列不为空:取出队首节点,如果节点为 null,那么 res 更新为 res + ‘#,’;如果节点不是 null,那么res 更新为 res + val,并且将节点的左右节点依次加入 queue。继续循环
- 队列为空:结束循环
- 返回"[" + res + “]”
-
反序列化流程如下
- 去掉字符串 res 前后的[和],并将其按照,逗号切分得到数组 nodes
- 初始化队列 queue,放入 nodes 的第一个值对应的节点,nodes 弹出第一个值
- 检查队列是否为空:
- 队列不为空。从 queue 取出队首元素。从 nodes 中取出第一个值和第二值,依次处理。继续循环
- 队列为空。结束循环
- 返回根节点
-
反序列化函数的设计关键是:
数组 nodes 取出元素的顺序和原二叉树层序遍历的顺序是对应的
-
代码实现
// 序列化
var serialize = function(root) {
if (!root) {
return "[]";
}
let res = "";
let node = root;
const queue = [node];
while (queue.length) {
const front = queue.shift();
if (front) {
res += `${front.val},`;
queue.push(front.left);
queue.push(front.right);
} else {
res += "#,";
}
}
res = res.substring(0, res.length - 1); // 去掉最后一个逗号
return `[${res}]`;
};
// 反序列化
var deserialize = function(data) {
if (data.length <= 2) {
return null;
}
const nodes = data.substring(1, data.length - 1).split(",");
const root = new TreeNode(parseInt(nodes[0]));
nodes.shift();
const queue = [root];
while (queue.length) {
const node = queue.shift();
// 第一个是左节点,节点为空,直接跳过
const leftVal = nodes.shift();
if (leftVal !== "#") {
node.left = new TreeNode(leftVal);
queue.push(node.left);
}
// 第二个是右节点,节点为空,直接跳过
const rightVal = nodes.shift();
if (rightVal !== "#") {
node.right = new TreeNode(rightVal);
queue.push(node.right);
}
}
return root;
};
6. 二叉搜索树的第 k 大节点
题目
- 给定一棵二叉搜索树,请找出其中第k大的节点
解题思路
- 中序遍历的顺序是左根右,是单调递增的序列;求第k大的数就是就倒数第k个元素的值
- 最终返回的倒数第k个元素就是
arr.length-1-k+1
,即以arr.length-k
为下标的元素
代码实现
var kthLargest = function(root, k) {
//中序遍历的模板代码
let arr=[];
function dfs(node){
if(!node) return;
dfs(node.left);
arr.push(node.val);
dfs(node.right);
}
dfs(root);
return arr[arr.length-k];
};
7. 二叉搜索树的最近公共祖先
-
题目
- 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先
最近公共祖先的定义
- 对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)
-
解题思路
- 由于
lowestCommonAncestor(root, p, q)
的功能是找出以root为根节点的两个节点p和q的最近公共祖先。 我们考虑:- 如果p和q分别在root两侧,那么root就是最近公共祖先。我们使用
(root.val - p.val) * (root.val - q.val) <= 0
来判断即可 - 否则的话(p和q在某一颗子树上),我们继续判断p是否在左子树。如果在(p在q一定也在),我们返回递归去左子树找。如果不在我们返回递归去右子树找
- 如果p和q分别在root两侧,那么root就是最近公共祖先。我们使用
- 由于
-
代码实现
var lowestCommonAncestor = function(root, p, q) {
if ((root.val - p.val) * (root.val - q.val) <= 0) return root
if (p.val < root.val) return lowestCommonAncestor(root.left, p, q)
return lowestCommonAncestor(root.right, p, q)
};
8. 二叉树的最近公共祖先
题目
- 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先
最近公共祖先的定义
- 对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)
解题思路
- 由于
lowestCommonAncestor(root, p, q)
的功能是找出以root为根节点的两个节点p和q的最近公共祖先。 我们考虑:- 如果p和q分别是root的左右节点,那么root就是我们要找的最近公共祖先
- 如果root是None,说明我们在这条寻址线路没有找到,我们返回None表示没找到
- 我们继续在左右子树执行相同的逻辑。
- 如果左子树没找到,说明在右子树,我们返回
lowestCommonAncestor(root.right, p , q)
- 如果右子树没找到,说明在左子树,我们返回
lowestCommonAncestor(root.left, p , q)
- 如果左子树和右子树分别找到一个,我们返回root
- 由于
代码实现
var lowestCommonAncestor = function(root, p, q) {
if (!root || root === p || root === q) return root;
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
if (!left) return right; // 左子树找不到,返回右子树
if (!right) return left; // 右子树找不到,返回左子树
return root;
};
排序
1. 把数组排成最小的数
题目
- 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个
解题思路
- 在 js 中,可以通过参数将自定义的「排序依据」作为函数传入 sort 中,这个函数的逻辑是:
- 如果 a + b < b + a,说明 ab 比 ba 小,a 应该在 b 前面,返回-1
- 如果 a + b > b + a,说明 ab 比 ba 大,a 应该在 b 后面,返回 1
- 如果相等,返回 0
- 在 js 中,可以通过参数将自定义的「排序依据」作为函数传入 sort 中,这个函数的逻辑是:
代码实现
var minNumber = function(nums) {
nums.sort((a, b) => {
const s1 = a + "" + b;
const s2 = b + "" + a;
if (s1 < s2) return -1;
if (s1 > s2) return 1;
return 0;
});
return nums.join("");
};
图
广度优先搜索
1. 从上到下打印二叉树 I
-
题目
- 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印
-
解题思路
- 将 root 放入队列
- 取出队首元素,将 val 放入返回的数组中
- 检查队首元素的子节点,若不为空,则将子节点放入队列
- 检查队列是否为空,为空,结束并返回数组;不为空,回到第二步
-
代码实现
var levelOrder = function(root) {
if (!root) {
return [];
}
const data = [];
const queue = [root];
while (queue.length) {
const first = queue.shift();
data.push(first.val);
first.left && queue.push(first.left);
first.right && queue.push(first.right);
}
return data;
};
2. 从上到下打印二叉树 II
-
题目
- 从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行
-
解题思路
- 初始化 queue,用于存储当前层的节点
- 检查 queue 是否为空
- 如果不为空:依次遍历当前 queue 内的所有节点,检查每个节点的左右子节点,将不为空的子节点放入 queue,继续循环
- 如果为空:跳出循环
-
代码实现
var levelOrder = function(root) {
if (!root) return [];
const queue = [root];
const res = []; // 存放遍历结果
let level = 0; // 代表当前层数
while (queue.length) {
res[level] = []; // 第level层的遍历结果
let levelNum = queue.length; // 第level层的节点数量
while (levelNum--) {
const front = queue.shift();
res[level].push(front.val);
if (front.left) queue.push(front.left);
if (front.right) queue.push(front.right);
}
level++;
}
return res;
};
3. 从上到下打印二叉树 III
-
题目
- 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推
-
解题思路
- 借助 level 变量标记层数,当 level 为偶数的时候,镜像翻转遍历结果
-
代码实现
var levelOrder = function(root) {
if (!root) return [];
const queue = [root];
const res = [];
let level = 0; // 代表当前层数
while (queue.length) {
res[level] = []; // 第level层的遍历结果
let levelNum = queue.length; // 第level层的节点数量
while (levelNum--) {
const front = queue.shift();
res[level].push(front.val);
if (front.left) queue.push(front.left);
if (front.right) queue.push(front.right);
}
// 行号是偶数时,翻转当前层的遍历结果
if (level % 2) {
res[level].reverse();
}
level++;
}
return res;
};
深度优先搜索
1. 二叉树的深度
-
题目
- 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度
-
解题思路
- 遍历左子树和右子树取最大深度+1
-
代码实现
var maxDepth = function(root) {
if(!root) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
};
2. 平衡二叉树
-
题目
- 输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树
-
解题思路
- 首先获取平衡二叉树的深度,判断左右子树的深度之差是不是<=1,并且需要判断这棵二叉树是不是同时具有左右子树
- Math.abs(x)=|x| 取某个数的绝对值
-
代码实现
var isBalanced = function(root) {
//获取深度
function getHeight(root){
if(!root) return 0;
return Math.max(getHeight(root.left),getHeight(root.right))+1;
}
if(!root) return true;
//平衡二叉树的判断条件
return isBalanced(root.left) && isBalanced(root.right)
&& Math.abs(getHeight(root.left)-getHeight(root.right))<=1;
};
3. 二叉树中和为某一值的路径
-
题目
- 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径
-
解题思路
- 先序遍历+回溯
-
代码实现
var pathSum = function(root, sum) {
var path=[], //保存路径
res=[]; //保存路经的数组
/*辅助函数---增加参数列表,用来实现对res,path的引用值的传递,因为res,path为数组,是对象范畴
本题目中需要根据条件,回溯更新路径path直到符合条件.
*/
var resuc = function (root, sum, res, path) {
if (root) {
//单个节点要做的事
path.push(root.val);
if (!root.left && !root.right && sum-root.val == 0) {
res.push([...path]);
}
//左右子节点递归调用
resuc(root.left, sum - root.val,res, path);
resuc(root.right, sum - root.val, res, path);
path.pop(); //回溯先序遍历一条路径结束,不符合条件时,将最后一个数弹出如5,4,4,7-->5,4,4,-2。
}
return res;
}
return resuc(root, sum, res, path);
};
总结
- 在力扣上看了《剑指Offer》,看大佬们用JavaScript刷题的思路,自己总结一下,以后有时间会更深一步学习一下算法,到时再更新