513. 找树左下角的值
本地递归偏难,反而迭代简单属于模板题, 两种方法掌握一下题目链接/文章讲解/视频讲解:代码随想录
重点:
1. 递归三部曲
思路:
递归法:
1. 确定参数和返回值
private void traversal(TreeNode cur, int depth)
2. 确定终止条件
if (cur.left == null && cur.right == null) { if (depth > maxDepth) {} }
3. 确定单层递归的逻辑
depth++; traversal(cur.left/right, depth); depth--;
迭代法:
1. 用队列记录节点,用size记录当前层还剩余几个节点没有处理
int maxDepth = Integer.MIN_VALUE;
int result;
public int findBottomLeftValue(TreeNode root) {
traversal(root, 1);
return result;
}
private void traversal(TreeNode cur, int depth) {
// 终止条件
// 遇到叶子节点,并判断是否为最深的来更新result
// 因为我们左永远比右进的早,所以肯定第一次depth大于maxdepth就更新好了result
if (cur.left == null && cur.right == null) {
if (depth > maxDepth) {
maxDepth = depth;
result = cur.val;
}
return;
}
// 左
if (cur.left != null) {
depth++;
traversal(cur.left, depth);
// 左边递归完回溯回中间
depth--;
}
// 右
if (cur.right != null) {
depth++;
traversal(cur.right, depth);
// 右边递归完回溯回中间
depth--;
}
}
public int findBottomLeftValue(TreeNode root) {
int result = 0;
Deque<TreeNode> queue = new LinkedList<>();
queue.offerLast(root);
while (!queue.isEmpty()) {
// 当前层有几个节点
int size = queue.size();
// 因为我们是先加左侧节点
// 第一个弹出的就是当前层最左侧的
result = queue.getFirst().val;
while (size > 0) {
TreeNode cur = queue.removeFirst();
if (cur.left != null) {
queue.offerLast(cur.left);
}
if (cur.right != null) {
queue.offerLast(cur.right);
}
size--;
}
}
return result;
}
112. 路径总和
本题又一次设计要回溯的过程,而且回溯的过程隐藏的还挺深,建议先看视频来理解
112. 路径总和 和 113. 路径总和II 一起做了。 优先掌握递归法。
题目链接/文章讲解/视频讲解:代码随想录
重点:
1. 递归函数什么时候需要返回值?什么时候不需要返回值?
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。
思路:
递归法:
1. 确定参数和返回值
private boolean traversal(TreeNode cur, int count)
2. 确定终止条件
遇到叶子节点,并且剩余总和未被减完或者刚刚好减完
if (cur.left == null && cur.right == null && count == 0) { return true; } if (cur.left == null && cur.right == null && count != 0) { return false; }
3. 确定单层递归的逻辑
count如果在下次递归无法刚刚好减掉,需要回溯到上一轮递归
if (cur.left/right != null) { count -= cur.left/right.val; if (traversal(cur.left/right, count)) { return true; } count += cur.left/right.val; }
迭代法:
1. 用两个栈,一个栈是用来递归节点的,一个栈用来记录到当前节点的路径上的数值和
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
return traversal(root, targetSum - root.val);
}
private boolean traversal(TreeNode cur, int count) {
// 终止条件
// 遇到叶子节点,检查count
if (cur.left == null && cur.right == null && count == 0) {
return true;
}
if (cur.left == null && cur.right == null && count != 0) {
return false;
}
// 左
if (cur.left != null) {
count -= cur.left.val;
if (traversal(cur.left, count)) {
return true;
}
// 回溯
count += cur.left.val;
}
// 右
if (cur.right != null) {
count -= cur.right.val;
if (traversal(cur.right, count)) {
return true;
}
// 回溯
count += cur.right.val;
}
return false;
}
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
Deque<TreeNode> nodeStack = new LinkedList<>();
Deque<Integer> sumStack = new LinkedList<>();
nodeStack.offerFirst(root);
sumStack.offerFirst(root.val);
while (!nodeStack.isEmpty()) {
TreeNode curNode = nodeStack.removeFirst();
int curSum = sumStack.removeFirst();
// 中
if (curNode.left == null && curNode.right == null && curSum == targetSum) {
return true;
}
// 右
if (curNode.right != null) {
nodeStack.offerFirst(curNode.right);
sumStack.offerFirst(curSum + curNode.right.val);
}
// 左
if (curNode.left != null) {
nodeStack.offerFirst(curNode.left);
sumStack.offerFirst(curSum + curNode.left.val);
}
}
return false;
}
113. 路径总和II
重点:
1. 因为需要处理所有路径,也不用对递归返回值进行处理,所以递归函数不需要返回值
思路:
递归法:
1. 确定参数及返回值
private void traversal(TreeNode cur, int count)
2. 确定终止条件
遇到叶子节点,并且路径上的值的和为target
if (cur.left == null && cur.right == null) { if (count - cur.val == 0) {} }
3. 确定单层递归的逻辑
前序遍历,开头就把当前的节点值加入到path中,然后走左边,把count处理好,调用递归函数,然后回溯count和path,最后处理右边
if (cur.left/right != null) { count -= cur.val; traversal(cur.left/right, count); // 两个回溯 count += cur.val; path.removeLast(); }
List<List<Integer>> result;
LinkedList<Integer> path;
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
result = new LinkedList<>();
path = new LinkedList<>();
if (root == null) {
return result;
}
traversal(root, targetSum);
return result;
}
private void traversal(TreeNode cur, int count) {
// 中
path.add(cur.val);
// 终止条件
// 遇到叶子节点,并且sum对的上
if (cur.left == null && cur.right == null) {
if (count - cur.val == 0) {
result.add(new LinkedList<>(path));
}
return;
}
// 左
if (cur.left != null) {
count -= cur.val;
traversal(cur.left, count);
// 两个回溯
count += cur.val;
path.removeLast();
}
// 右
if (cur.right != null) {
count -= cur.val;
traversal(cur.right, count);
// 两个回溯
count += cur.val;
path.removeLast();
}
}
106. 从中序与后序遍历序列构造二叉树
本题算是比较难的二叉树题目了,大家先看视频来理解。106.从中序与后序遍历序列构造二叉树,105.从前序与中序遍历序列构造二叉树 一起做,思路一样的
题目链接/文章讲解/视频讲解: 代码随想录重点:1. 循环不变量原则2. 先分解中序数组,再分解后序数组思路:递归法:1. 确定参数及返回值因为我们需要对递归返回值进行left/right赋值,所以递归函数需要返回值private TreeNode traversal(int[] inorder, int inorderStart, int inorderEnd, int[] postorder, int postorderStart, int postorderEnd)
2. 确定终止条件
当postorder数组中只有一个元素的时候,说明这个元素为叶子节点
if (postorderEnd - postorderStart == 1) { return curRoot; }
3. 确定单层递归的逻辑
我们知道后序数组中的最后一个即为当前的中节点,我们拿着这个中节点去中序(左中右)数组中可以分割开左和右,再拿着左和右去分割后序数组。拿到对应的索引后接着下一轮的递归。// 循环不变量 左闭右开原则 // 中序数组的索引切割 int leftInorderStart = inorderStart; int leftInorderEnd = curRootIndex; int rightInorderStart = curRootIndex + 1; int rightInorderEnd = inorderEnd; // 后序数组的索引切割 int leftPostorderStart = postorderStart; int leftPostorderEnd = postorderStart + (curRootIndex - inorderStart); int rightPostorderStart = leftPostorderEnd; int rightPostorderEnd = postorderEnd - 1; curRoot.left = traversal(inorder, leftInorderStart, leftInorderEnd, postorder, leftPostorderStart, leftPostorderEnd); curRoot.right = traversal(inorder, rightInorderStart, rightInorderEnd, postorder, rightPostorderStart, rightPostorderEnd);
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (inorder.length == 0 || postorder.length == 0) {
return null;
}
return traversal(inorder, 0, inorder.length, postorder, 0, postorder.length);
}
private TreeNode traversal(int[] inorder, int inorderStart, int inorderEnd, int[] postorder, int postorderStart, int postorderEnd) {
if (postorderStart == postorderEnd) {
return null;
}
int rootVal = postorder[postorderEnd - 1];
TreeNode curRoot = new TreeNode(rootVal);
// 终止条件
if (postorderEnd - postorderStart == 1) {
return curRoot;
}
// 单层递归的逻辑
// 获取当前节点在中序数组的索引就知道当前节点的左右都分别有什么
int curRootIndex;
for (curRootIndex = inorderStart; curRootIndex < inorderEnd; curRootIndex++) {
if (inorder[curRootIndex] == rootVal) {
break;
}
}
// 循环不变量原则
// 先切割中序数组
// 中序数组的索引切割
int leftInorderStart = inorderStart;
int leftInorderEnd = curRootIndex;
int rightInorderStart = curRootIndex + 1;
int rightInorderEnd = inorderEnd;
// 后序数组的索引切割
int leftPostorderStart = postorderStart;
int leftPostorderEnd = postorderStart + (curRootIndex - inorderStart);
int rightPostorderStart = leftPostorderEnd;
int rightPostorderEnd = postorderEnd - 1;
curRoot.left = traversal(inorder, leftInorderStart, leftInorderEnd, postorder, leftPostorderStart, leftPostorderEnd);
curRoot.right = traversal(inorder, rightInorderStart, rightInorderEnd, postorder, rightPostorderStart, rightPostorderEnd);
return curRoot;
}
105. 从前序与中序遍历序列构造二叉树
题目链接/文章讲解/视频讲解:代码随想录
重点:
1. 循环不变量原则
2. 通过前序获得当前根节点,再用这个根节点先切割中序知道左和右,再反过来切割前序,接着下次递归
思路:
递归法:
1. 确定参数及返回值
因为需要遍历所有路径及处理递归返回值,所以需要返回值
private TreeNode traversal(int[] preorder, int preorderStart, int preorderEnd, int[] inorder, int inorderStart, int inorderEnd)
2. 确定终止条件
遇到了叶子节点,叶子节点的left和right不需要额外赋值
if (preorderEnd - preorderStart == 1) { return curRoot; }
3. 确定单层递归的逻辑
因为前序数组,第一个肯定是当前节点,我们拿着这个节点获取它在中序数组的索引,用这个索引切割左和右,知道左和右之后再切割前序数组。前序数组需要抛弃第一个,因为第一个是当前节点,已经遍历过了
// 中序的切割 // 知道了左和右 int leftInorderStart = inorderStart; int leftInorderEnd = curRootIndex; int rightInorderStart = curRootIndex + 1; int rightInorderEnd = inorderEnd; // 前序的切割 // 知道左和右之后好切割前序 int leftPreorderStart = preorderStart + 1; int leftPreorderEnd = leftPreorderStart + (curRootIndex - inorderStart); int rightPreorderStart = leftPreorderEnd; int rightPreorderEnd = preorderEnd; // 递归的赋值left和right curRoot.left = traversal(preorder, leftPreorderStart, leftPreorderEnd, inorder, leftInorderStart, leftInorderEnd); curRoot.right = traversal(preorder, rightPreorderStart, rightPreorderEnd, inorder, rightInorderStart, rightInorderEnd);
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder.length == 0 || inorder.length == 0) {
return null;
}
return traversal(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
private TreeNode traversal(int[] preorder, int preorderStart, int preorderEnd, int[] inorder, int inorderStart, int inorderEnd) {
if (preorderStart == preorderEnd) {
return null;
}
int curRootVal = preorder[preorderStart];
TreeNode curRoot = new TreeNode(curRootVal);
// 终止条件
// 前序数组只有一个元素了,说明当前节点为叶子节点,不需要对left和right赋值
if (preorderEnd - preorderStart == 1) {
return curRoot;
}
// 获取当前中节点的中序索引
// 知道了左和右
int curRootIndex;
for (curRootIndex = inorderStart; curRootIndex < inorder.length; curRootIndex++) {
if (inorder[curRootIndex] == curRootVal) {
break;
}
}
// 中序的切割
int leftInorderStart = inorderStart;
int leftInorderEnd = curRootIndex;
int rightInorderStart = curRootIndex + 1;
int rightInorderEnd = inorderEnd;
// 前序的切割
// 知道左和右之后,用左的长度就可以切割
int leftPreorderStart = preorderStart + 1;
int leftPreorderEnd = leftPreorderStart + (curRootIndex - inorderStart);
int rightPreorderStart = leftPreorderEnd;
int rightPreorderEnd = preorderEnd;
// 递归的赋值left和right
curRoot.left = traversal(preorder, leftPreorderStart, leftPreorderEnd, inorder, leftInorderStart, leftInorderEnd);
curRoot.right = traversal(preorder, rightPreorderStart, rightPreorderEnd, inorder, rightInorderStart, rightInorderEnd);
return curRoot;
}