文章目录
110. 平衡二叉树
- 在写getHeigh的时候思路有点乱了,能够得到某节点的高度,但如何与平衡二叉树构建联系就想不到了,递归还是得多练
var isBalanced = function(root) {
const getHerght = (node) => {
if (!node) return 0;
let left = getHerght(node.left);
// 当左右子树中不平衡时,直接返回-1
if (left === -1) return -1;
let right = getHerght(node.right);
if (right === -1) return -1;
// 返回值 -1 或 非-1
return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
}
return getHerght(root) === -1 ? false : true;
};
- 本题还可以用迭代解,有点复杂这里不写了
257. 二叉树的所有路径
- 需要找到根节点的所有路,自然就会想到利用前序遍历
- 与之前不同的是,在函数中我们要传入一个当前的路径字符串(这也是我卡住的原因,想不到有什么方法能够存储每一个路径)
var binaryTreePaths = function(root) {
let res = [];
const getPath = (node, curPath) => {
if (!node.left && !node.right) {
curPath += node.val;
res.push(curPath);
return;
}
// 中左右
// 这里->和val的顺序不同会导致结尾代码的不同
curPath += node.val + "->";
node.left && getPath(node.left, curPath);
node.right && getPath(node.right, curPath);
}
getPath(root, "");
return res;
};
404. 左叶子之和
- 比较简单,先上一个野办法(〃‘▽’〃),深度遍历同时累加
const dfs = (node) => {
if (!node) return;
if (node.left) {
// 叶子节点
if (!node.left.left && !node.left.right) {
res += node.left.val;
} else {
dfs(node.left);
}
}
node.right && dfs(node.right);
}
dfs(root);
return res;
};
- 标准递归,平时在递归时我们都是依靠子节点判断当前节点的属性,这里是靠父节点判断当前节点
var sumOfLeftLeaves = function(root) {
//采用后序遍历 递归遍历
// 1. 确定递归函数参数
const nodesSum = function(node){
// 2. 确定终止条件
if(node===null){
return 0;
}
let leftValue = nodesSum(node.left);
let rightValue = nodesSum(node.right);
// 3. 单层递归逻辑
let midValue = 0;
if(node.left && node.left.left === null && node.left.right===null){
midValue = node.left.val;
}
let sum = midValue + leftValue + rightValue;
return sum;
}
return nodesSum(root);
};
- 也可以不用midValue
var sumOfLeftLeaves = function(root) {
if (!root) return 0;
let leftVal = sumOfLeftLeaves(root.left);
if (root.left && !root.left.left && !root.left.right) leftVal = root.left.val;
let rightVal = sumOfLeftLeaves(root.right);
return leftVal + rightVal;
};
513. 找树左下角的值
- 写不出递归,先上个迭代(T ^ T)
var findBottomLeftValue = function(root) {
let que = [root], res = root.val;
while (que.length) {
let n = que.length;
for (let i = 0; i < n; i++) {
let node = que.shift();
if (i === 0) res = node.val;
node.left && que.push(node.left);
node.right && que.push(node.right);
}
}
return res;
};
- 然后补一下递归,以左子树优先,找深度最大的节点
var findBottomLeftValue = function(root) {
//首先考虑递归遍历 前序遍历 找到最大深度的叶子节点即可
let maxPath = 0,resNode = null;
// 1. 确定递归函数的函数参数
const dfsTree = function(node,curPath){
// 2. 确定递归函数终止条件
if(node.left===null&&node.right===null){
if(curPath>maxPath){
maxPath = curPath;
resNode = node.val;
}
// return ;
}
node.left&&dfsTree(node.left,curPath+1);
node.right&&dfsTree(node.right,curPath+1);
}
dfsTree(root,1);
return resNode;
};
112. 路径总和
- 需要加上一个计数器来记录当前路径的总和,这里用加或减都行作为函数的一个参数,只是不要写在函数外,这样之后还需要减掉会比较麻烦
var hasPathSum = function(root, targetSum) {
const traversal = (node, cnt) => {
// 找到叶子节点,且总和符合要求
if (cnt === 0 && !node.left && !node.right) return true;
// 找到叶子节点,总和不等
if (!node.left && !node.right) return false;
// 遇到叶子节点返回true时,直接返回true
if (node.left && traversal(node.left, cnt - node.left.val)) return true;
if (node.right && traversal(node.right, cnt - node.right.val)) return true;
return false;
}
if (!root) return false;
return traversal(root, targetSum - root.val);
};
递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (236. 二叉树的最近公共祖先)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
113. 路径总和 II
- 总的来说和上一题的思路相近,但是我们再将符合条件的路径放入res中时,需要注意深拷贝和浅拷贝的区别,接下来对此着重介绍
引用数据类型
也就是对象类型Object type,比如:Object 、Array 、Function 、Data等。javascript的引用数据类型是「保存在堆内存中的对象」。
「与其他语言的不同是,你不可以直接访问堆内存空间中的位置和操作堆内存空间。只能操作对象在栈内存中的引用地址。」
浅拷贝:如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,无论对新旧数组的哪一个进行了修改,两者都会发生变化。
深拷贝:完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
- 我们在将路径保存到res中时,若使用浅拷贝res.push(curPath)我们在之后修改curPath时也会修改res中的结果,而深拷贝的数据是相同内容但不同地址的,后续修改与其无关
与题无关(关于…拓展运算符):用扩展运算符对数组或者对象进行拷贝时,只能扩展和深拷贝第一层的值,对于第二层极其以后的值,扩展运算符将不能对其进行打散扩展,也不能对其进行深拷贝,即拷贝后和拷贝前第二层中的对象或者数组仍然引用的是同一个地址,其中一方改变,另一方也跟着改变。
var pathSum = function (root, targetSum) {
let res = [];
const traversal = (node, cnt, curPath) => {
if (cnt === 0 && !node.left && !node.right) {
// 特别注意:深拷贝深拷贝
res.push([...curPath]);
return;
}
if (!node.left && !node.right) return;
if (node.left) {
curPath.push(node.left.val)
traversal(node.left, cnt - node.left.val, curPath);
curPath.pop();
}
if (node.right) {
curPath.push(node.right.val)
traversal(node.right, cnt - node.right.val, curPath);
curPath.pop();
}
return;
}
if (!root) return [];
traversal(root, targetSum - root.val, [root.val]);
return res;
};
106. 从中序与后序遍历序列构造二叉树
- 首先回忆一下如何根据两个顺序构造一个唯一的二叉树,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
- 最重要的就是数组切割
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
var buildTree = function(inorder, postorder) {
if (!inorder.length) return null;
const rootVal = postorder.pop();
let index = inorder.indexOf(rootVal);
const root = new TreeNode(rootVal);
// slice左闭右开
root.left = buildTree(inorder.slice(0,index), postorder.slice(0,index));
root.right = buildTree(inorder.slice(index + 1), postorder.slice(index));
return root;
};
654. 最大二叉树
- 构造树一般采用前序遍历,因为先构造中间节点,然后是左右子树
- 第一种写法是通过分割数组在左右子树中传入对应的子数组,当数组中没有值时,说明是空节点
var constructMaximumBinaryTree = function(nums) {
const buildTree = (arr) => {
// 终止条件,数组中没有值
if (!arr.length) return null;
let maxValue = -1;
let maxIndex = -1;
for (let i = 0; i < arr.length; i++) {
if (arr[i] > maxValue) {
maxValue = arr[i];
maxIndex = i;
}
}
let node = new TreeNode(maxValue);
node.left = buildTree(arr.slice(0, maxIndex));
node.right = buildTree(arr.slice(maxIndex + 1));
return node;
}
return buildTree(nums);
};
- 第二种写法是传入同一个数组,但是通过限定起始和结尾来变相的切割数组,可以节省空间
var constructMaximumBinaryTree = function(nums) {
const buildTree = (arr, left, right) => {
if (left >= right) return null;
let maxValue = -1; // 复习:js的最大值 Number.MAX_VALUE
let maxIndex = -1;
for (let i = left; i < right; i++) {
if (arr[i] > maxValue) {
maxValue = arr[i];
maxIndex = i;
}
}
let node = new TreeNode(maxValue);
node.left = buildTree(arr, left, maxIndex);
node.right = buildTree(arr, maxIndex + 1, right);
return node;
}
return buildTree(nums, 0, nums.length);
};
617. 合并二叉树
- 对两数同时遍历(没有顺序要求)
var mergeTrees = function(root1, root2) {
const preOrder = (root1, root2) => {
if (!root1) return root2;
if (!root2) return root1;
root1.val += root2.val;
root1.left = preOrder(root1.left, root2.left);
root1.right = preOrder(root1.right, root2.right);
return root1;
}
return preOrder(root1, root2);
};
700. 二叉搜索树中的搜索
- 也是比较简单的题目,主要是要利用二叉搜索树的性质
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
var searchBST = function(root, val) {
if (!root) return null;
if (root.val === val) return root;
if (root.val < val) return searchBST(root.right, val);
// 注意这里的return,如果不写会没有返回值
if (root.val > val) return searchBST(root.left, val);
};