代码随想录算法营DAY18 | 513.找树左下角的值 112. 路径总和 113.路径总和ii 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树
513.找树左下角的值
题目链接: 513.找树左下角的值
思路:
这一题用层序遍历比较方便。但一定记得层序遍历每一层for循环的时候要用一个固定变量len记录循环次数,不能用que.length(因为这个是变化的)
代码:
var findBottomLeftValue = function(root) {
let que = [];
que.push(root);
let start;
while(que.length){
let len=que.length;//这里一定要有一个固定的len记录!!!因为que.length会变化
for(let i=0;i<len;i++){
let cur=que.shift();
if(i===0) start=cur.val;
if(cur.left) que.push(cur.left);
if(cur.right) que.push(cur.right);
}
}
return start;
};
复杂度分析:
112. 路径总和
题目链接: 112. 路径总和
思路:
找一条路径的题目,基本上一定要返回值,并且涉及到返回值的判断和处理问题。本题也要注意回溯。
1.确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
2.确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
代码:
//思路版本
var hasPathSum = function (root, targetSum) {
function getSum(node, targetSum) {
if (node === null) return false;
targetSum -= node.val;
if (!node.left && !node.right && targetSum === 0) return true;
else if(!node.left && !node.right) return false;
if(node.left){
if(getSum(node.left, targetSum))return true;
}
if(node.right){
if(getSum(node.right, targetSum))return true;
targetSum += node.val;
}
return false;
}
let a = getSum(root, targetSum);
return a;
};
//简化版本
var hasPathSum = function (node, targetSum) {
if (node === null) return false;
if (!node.left && !node.right && targetSum === node.val) return true;
return hasPathSum(node.right, targetSum-node.val)||hasPathSum(node.left, targetSum-node.val);
};
113.路径总和ii
题目链接: 113.路径总和ii
思路:
这一题与112不同,要遍历整棵树且不用处理返回值,所以直接return就行。
代码:
var pathSum = function(root, targetSum) {
let result = [];//记录结果
let stack = [];//记录每条路径
function getPath(node,sum) {
if (node === null) return;
stack.push(node.val);
if (node.left) getPath(node.left, sum - node.val);
if (node.right) getPath(node.right, sum - node.val);
if (!node.left && !node.right && node.val === sum) {//为叶子节点且sum相等时返回路径
result.push([...stack]);//这里是什么意思,浅拷贝和深拷贝?
}
stack.pop();//回溯
return;
}
getPath(root, targetSum);
return result;
};
问题:
这里的返回值类型是什么,为什么要深拷贝[…stack]
106.从中序与后序遍历序列构造二叉树
题目链接: 106.从中序与后序遍历序列构造二叉树
思路:
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
代码:
var buildTree = function(inorder, postorder) {
if(!inorder.length) return null;
let root=new TreeNode(postorder.pop());
let k=inorder.indexOf(root.val);
root.left=buildTree(inorder.slice(0,k),postorder.slice(0,k));
root.right=buildTree(inorder.slice(k+1),postorder.slice(k));
return root;
};
问题:
这里slice方法可能不能用(k,-1)的下标形式?(正负下标不能同时出现)?刚开始后序数组没用pop,弄了半天也不知道哪里出错了,改了pop就好了,应该是下标问题…
105.从前序与中序遍历序列构造二叉树
题目链接: 105.从前序与中序遍历序列构造二叉树
思路:
和后序中序一样,只不过变成了在前序数组中找根节点的时候取最前面的元素作为根节点。
代码:
var buildTree = function(preorder, inorder) {
if(!inorder.length) return null;
let root=new TreeNode(preorder.shift());//这里变成了shift
let k=inorder.indexOf(root.val);
root.left=buildTree(preorder.slice(0,k),inorder.slice(0,k));
root.right=buildTree(preorder.slice(k),inorder.slice(k+1));
return root;
};
感想总结
(1)递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
1.如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
2.如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
3.如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(112)
(2)浅拷贝与深拷贝
(3)数组方法slice
slice.(a,b)相当于取[a,b)区间