二叉树的遍历与处理
例如,
swap
:处理root
节点invertTree(root->left)
:在其内部,会处理root->left
invertTree(root->right)
:在其内部,会处理root->right
因此,无论是swap
还是inverTree
,都是在处理。因此,他们的顺序,就是遍历的顺序。
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右
return root;
}
};
对于404左叶子之和,不是很直观:实际上是后序遍历。可以体会一下。
只有先计算出左孩子、右孩子的对应值,才会计算该节点的对应值。计算之后,再返回。
// 不直观
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
return getLeftSum(root);
}
private int getLeftSum(TreeNode root){
// 空节点
if(root==null) return 0;
// 左孩子是叶子节点
if(root.left!=null && root.left.left==null && root.left.right==null) return root.left.val + getLeftSum(root.right);
else return getLeftSum(root.left) + getLeftSum(root.right);
}
}
// 代码随想录版本
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if (root == null) return 0;
int leftValue = sumOfLeftLeaves(root.left); // 左
int rightValue = sumOfLeftLeaves(root.right); // 右
int midValue = 0;
if (root.left != null && root.left.left == null && root.left.right == null) {
midValue = root.left.val;
}
int sum = midValue + leftValue + rightValue; // 中
return sum;
}
}
513. 找树左下角的值
class Solution {
public int findBottomLeftValue(TreeNode root) {
Deque<TreeNode> dq = new LinkedList<>();
dq.offer(root);
int count = dq.size();
TreeNode res = new TreeNode();
while(!dq.isEmpty()){
res = dq.poll(); count--;
if(res.left!=null) dq.offer(res.left);
if(res.right!=null) dq.offer(res.right);
while(count>0){
TreeNode node = dq.poll(); count--;
if(node.left!=null) dq.offer(node.left);
if(node.right!=null) dq.offer(node.right);
}
count=dq.size();
}
return res.val;
}
}
112. 路径总和Ⅰ
回溯:sum
或者target
,在作为回溯的参数时临时改变了。
姑且也算是回溯吧……
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
return backtracking(root, 0, targetSum);
}
private boolean backtracking(TreeNode node, int sum, int targetSum){ // sum:不包括node
sum += node.val;
// 叶子节点
if(node.left==null && node.right==null) return sum==targetSum? true: false;
// 非叶子节点
else if(node.left!=null && node.right==null) return backtracking(node.left, sum, targetSum);
else if(node.right!=null && node.left==null) return backtracking(node.right, sum, targetSum);
else return backtracking(node.left, sum, targetSum) || backtracking(node.right, sum, targetSum);
}
}
代码随想录:
- 判空逻辑:空节点、叶子节点
sum
与targetSum
:合并成为一个,表示与目标的差值。- 回溯思想:
target = targetSum-root.val
- 判断逻辑:因为会判空,所以不需要考虑迭代的子节点是否为空
// lc112 简洁方法
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false; // 为空退出
// 叶子节点判断是否符合
if (root.left == null && root.right == null) return root.val == targetSum;
// 求两侧分支的路径和
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
}
}
113. 路径总和Ⅱ(重点:回溯!回溯!)
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
result = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
backtracking(root, targetSum, path);
return result;
}
private List<List<Integer>> result;
private void backtracking(TreeNode node, int targetSum, ArrayList<Integer> path){
// 边缘条件
if(node==null) return;
path.add(node.val);
if(node.left==null && node.right==null){
if(targetSum == path.stream().reduce(0, Integer::sum)) result.add((List<Integer>)path.clone());
return;
}
// 递归与回溯
if(node.left!=null) {
backtracking(node.left, targetSum, path);
path.remove(path.size()-1);
}
if(node.right!=null){
backtracking(node.right, targetSum, path);
path.remove(path.size()-1);
}
}
}
思路:
- 目的是找到所有结果:没有返回值(因为需要遍历整棵树)
- 不涉及多线程,可以将
path
和result
放在成员变量,就不用放在形参了。 - 判空:空节点也可,叶子节点也可。
- 求和:用
target = targetSum - node.val...
,一个值代替两个值。 - 回溯:路径需要回溯。但是target不需要……我还是不认为target是回溯。
- 使用LinkedList:可以很方便remove最后一个元素。
- 可以直接添加
new LinkedList<>(path)
,并不需要clone()
- 递归与回溯不是一一对应?
实际上还是对应着的,只不过统一安排在本层末尾回溯,而不是上一层回溯下一层的。回溯完后,最终返回上一层。
如果恰好是那个叶节点,添加了result之后,却没有直接return
,最后也会在本层末尾回溯。
如果还有孩子,无论是空还是非空,此时下一层回溯不会添加元素,即使添加了,末尾也会回溯。自然不可再remove;
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res; // 非空判断
List<Integer> path = new LinkedList<>();
preOrderDfs(root, targetSum, res, path);
return res;
}
public void preOrderDfs(TreeNode root, int targetSum, List<List<Integer>> res, List<Integer> path) {
path.add(root.val);
// 遇到了叶子节点
if (root.left == null && root.right == null) {
// 找到了和为 targetsum 的路径
if (targetSum - root.val == 0) {
res.add(new ArrayList<>(path));
}
return; // 如果和不为 targetsum,返回
}
if (root.left != null) {
preOrderDfs(root.left, targetSum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
if (root.right != null) {
preOrderDfs(root.right, targetSum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
}
}
// 解法2
class Solution {
List<List<Integer>> result;
LinkedList<Integer> path;
public List<List<Integer>> pathSum (TreeNode root,int targetSum) {
result = new LinkedList<>();
path = new LinkedList<>();
travesal(root, targetSum);
return result;
}
private void travesal(TreeNode root, int count) {
if (root == null) return;
path.offer(root.val);
count -= root.val;
if (root.left == null && root.right == null && count == 0) {
result.add(new LinkedList<>(path));
}
travesal(root.left, count);
travesal(root.right, count);
path.removeLast(); // 回溯
}
}
106. 从中序与后序遍历序列构造二叉树(两个顺序构造一个唯一的二叉树)
以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
每次递归,都定义了新数组,耗时耗空间。
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
result = new TreeNode();
build(inorder, postorder, result);
return result;
}
private TreeNode result;
private void build(int[] inorder, int[] postorder, TreeNode node){
// 赋值
int length = postorder.length, leftLength = 0, rightLength = 0;
node.val = postorder[length - 1];
// 分割:左闭右闭?
for(int i=0; i<length; i++){
if(inorder[i]==node.val){
leftLength = i;
break;
}
}
rightLength = length - leftLength - 1;
if(leftLength!=0) {
TreeNode leftNode = new TreeNode();
node.left = leftNode;
build(Arrays.copyOfRange(inorder, 0, leftLength),
Arrays.copyOfRange(postorder, 0, leftLength),
leftNode);
}
if(rightLength!=0){
TreeNode rightNode = new TreeNode();
node.right = rightNode;
build(Arrays.copyOfRange(inorder, leftLength+1, length),
Arrays.copyOfRange(postorder, leftLength, length-1),
rightNode);
}
}
}
代码随想录
- 下标索引分割:分别由start与end
- map:快速获取位置。(树中没有重复节点)
class Solution {
Map<Integer, Integer> map; // 方便根据数值查找位置
public TreeNode buildTree(int[] inorder, int[] postorder) {
map = new HashMap<>();
for (int i = 0; i < inorder.length; i++) { // 用map保存中序序列的数值对应位置
map.put(inorder[i], i);
}
return findNode(inorder, 0, inorder.length, postorder,0, postorder.length); // 前闭后开
}
public TreeNode findNode(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd) {
// 参数里的范围都是前闭后开
if (inBegin >= inEnd || postBegin >= postEnd) { // 不满足左闭右开,说明没有元素,返回空树
return null;
}
int rootIndex = map.get(postorder[postEnd - 1]); // 找到后序遍历的最后一个元素在中序遍历中的位置
TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点
int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定后序数列的个数
root.left = findNode(inorder, inBegin, rootIndex,
postorder, postBegin, postBegin + lenOfLeft);
root.right = findNode(inorder, rootIndex + 1, inEnd,
postorder, postBegin + lenOfLeft, postEnd - 1);
return root;
}
}