513.找树左下角的值

层序遍历模板题。

//C#
public class Solution {
    public int FindBottomLeftValue(TreeNode root) {
        Queue<TreeNode> que = new Queue<TreeNode>();
        int res = 0;
        que.Enqueue(root);
        while (que.Count != 0) {
            int size = que.Count;
            for (int i = 0; i < size; i++) {
                TreeNode cur = que.Dequeue();
                if (i == 0) {
                    res = cur.val;
                }         
                if (cur.left != null) que.Enqueue(cur.left);
                if (cur.right != null) que.Enqueue(cur.right);
            }
        }
        return res;
    }
}

递归方式,保证先做后右,并记录最大深度就是靠左的最大深度节点了,同时注意巧妙的控制返回条件,让return的时候是叶子结点,并保证没有空指针。

这里有一个误区,左边最大深度的节点,不一定是左子节点,对于递归来说,先遍历根节点的左的左,没有就是左的右,但都是左半部分,符合递归的顺序。

//C++ 代码随想录,回溯感觉还是怪
class Solution {
public:
    int maxDepth = INT_MIN;
    int result;
    void traversal(TreeNode* root, int depth) {
        if (root->left == NULL && root->right == NULL) {
            if (depth > maxDepth) {
                maxDepth = depth;
                result = root->val;
            }
            return;
        }
        if (root->left) {
          //depth++;
            traversal(root->left, depth + 1); // 隐藏着回溯
          //depth--;
        }
        if (root->right) {
          //depth++;
            traversal(root->right, depth + 1); // 隐藏着回溯
          //depth--
        }
        return;
    }
    int findBottomLeftValue(TreeNode* root) {
        traversal(root, 0);//这里应该是1,才符合深度从1开始计算。
        return result;
    }
};

其实还是这样更清晰,避免显式回溯。

class Solution {
public:
    int result;
    int maxDepth = INT_MIN;
    int findBottomLeftValue(TreeNode* root) {
        doit(root, 1);
        return result;
    }
    void doit(TreeNode* cur, int depth) {
        depth++;
        if (cur->left == nullptr && cur->right == nullptr) {
            if (depth > maxDepth) {
                maxDepth = depth;   
                result = cur->val;
                cout<<depth;
            }
        }
        if (cur->left != nullptr) doit(cur->left, depth);
        if (cur->right != nullptr) doit(cur->right, depth);
    }
};

112. 路径总和

可以不用记录路径值求和的,Count-一直到叶子节点为0也符合条件,注意有返回值的递归在单层递归这一步一定要考虑返回值,比如这里我一开傲视就没有考虑左右遍历的时候return的逻辑,如果子递归都为true了,那直接返回true就行,一定要返回,要不然自递归的结果没法向上返回,终止条件的return只是边界条件(day17求路径那个不用返回是因为结果都保存在result数组里了,递归是void类型)。

alt

class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        vector<int> path;
        if (root == nullptr) return false;
        return getPath(root, path, targetSum);
    }
    bool getPath(TreeNode* cur, vector<int> path, int targetSum) {
        path.push_back(cur->val);
        if (cur->left == nullptr && cur->right == nullptr) {
            int sum = 0;
            for (int i =0; i < path.size(); i++) {
                sum += path[i];
            }
            if (sum == targetSum) return true;
            return false;
        }

        if (cur->left != nullptr) {
            if (getPath(cur->left, path, targetSum)) return true;
        }
        if (cur->right != nullptr) {
            if (getPath(cur->right, path, targetSum)) return true;
        }
        return false;
    }
};

回溯的时候注意恢复的是下一层的值,对于有返回值的递归,如果是bool类型返回值上层不再做操作,一路return就行,只有上层需要操作的时候再回溯。

//C#
public class Solution {
    public int cnt;
    public bool HasPathSum(TreeNode root, int targetSum) {
        if (root == null) return false;
        cnt = targetSum;
        return HasPath(root);
    }
    public bool HasPath(TreeNode cur) {
        Console.WriteLine(cnt);
        cnt -= cur.val;
        if (cur.left == null && cur.right == null) {
            if (cnt == 0) return true;
            return false;
        }
        if(cur.left != null) {
            if(HasPath(cur.left))
            return true;
            cnt += cur.left.val;
        }
        if (cur.right != null) {
            if (HasPath(cur.right))
            return true;
            cnt += cur.right.val;
        }
        
        return false;
    }
}

113.路径总和ii

求路径的时候记录一下path,注意path和cnt都要回溯

//C++ 全局变量要回溯
class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    int cnt;
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) return result;
        cnt = targetSum;
        doit(root);
        return result;
    }
    void doit(TreeNode* cur) {
        cout << cnt << endl;
        cnt -= cur->val;
        path.push_back(cur->val);
        if (cur->left == nullptr && cur->right == nullptr) {
            if (cnt == 0) {
                result.push_back(path);
            }
            return;
        }
        if (cur->left != nullptr) {
            doit(cur->left);
            path.pop_back();
            cnt += cur->left->val;
        }
        if (cur->right != nullptr) {
            doit(cur->right);
            path.pop_back();
            cnt += cur->right->val;
        }
    }
};

利用参数复制避免回溯,如果递归的时候把结果数组作为参数传递,利用复制来避免回溯,比引用传参和全局变量要多开空间呀,毕竟每调用一次递归都要复制一次数组(C#里要new一次List),其实就是浅拷贝(只拷贝地址)和深拷贝(开辟新空间)。

public class Solution {

    List<IList<int>> res = new List<IList<int>>();
    public IList<IList<int>> PathSum(TreeNode root, int targetSum) {
        if (root == null) return res;
        doit(root, targetSum, new List<int>());
        return res;
    }
    public void doit(TreeNode cur, int cnt, List<int> path1) {
        List<int> path = new List<int>(path1);
        cnt -= cur.val;
        path.Add(cur.val);
        if (cur.left == null && cur.right == null) {
            if (cnt == 0) {
                res.Add(path);
            }
            return;
        }
        if (cur.left != null) {
            doit(cur.left, cnt, path);
        }
        if (cur.right != null) {
            doit(cur.right, cnt, path);
        }
    }
}

106.从中序与后序遍历序列构造二叉树

  • 大体思路就是先用后序数组内的最后一个中(根)元素切中序得到左右区间,再反过来根据中序切出来的区间切后序的左右区间,重复这个过程直到切到叶子结点,树也就构造完了。
  • 切出来的区间定义要一致,要么都是左闭右闭,要么都是左闭右开。
  • 中序是必须的,因为中序能够根据中间节点的位置把左右子树分开。
  • 切割完的左右中序数组大小一定是和后序数组的大小相同的(这是必然)。
  • 后序提供节点元素,前序提供左右子树。
  • 构造的依据其实还是后序序列(递归停止的条件也是依据此),中序序列的作用是辅助对后序序列进行切割。 alt
//C++
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return nullptr;
        return doit(inorder, postorder);
    }

    TreeNode* doit(vector<int> & inorder, vector<int>& postorder) {
        //1.分割产生了空节点,返回null
        if (postorder.size() == 0) return nullptr;
        //2.后序不为空,那么取后序数组最后一个元素作为节点元素
        int rootValue = postorder[postorder.size() - 1];
        TreeNode* root = new TreeNode(rootValue);
        //叶子结点返回,不需要处理下面的左右区间(从下往上回溯构造)
        if (postorder.size() == 1) return root;
        //3.寻找中切割点
        int index;
        for (index = 0; index < inorder.size(); index++) {
            if (inorder[index] == rootValue) break;
        }
        //4.切割中序数组得到左右中序区间
        //左闭右开 [0, index)
        vector<int> leftInorder(inorder.begin(), inorder.begin() + index);
        //[index + 1, end)
        vector<int> rightInorder(inorder.begin() + index + 1, inorder.end());

        //5.根据左中序区间长度切割后序数组
        postorder.resize(postorder.size() - 1);
        vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
        vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
                // 以下为日志
        cout << "----------" << endl;

        cout << "leftInorder :";
        for (int i : leftInorder) {
            cout << i << " ";
        }
        cout << endl;

        cout << "rightInorder :";
        for (int i : rightInorder) {
            cout << i << " ";
        }
        cout << endl;

        cout << "leftPostorder :";
        for (int i : leftPostorder) {
            cout << i << " ";
        }
        cout << endl;
         cout << "rightPostorder :";
        for (int i : rightPostorder) {
            cout << i << " ";
        }
        cout << endl;
        //6.递归处理左右区间
        root->left = doit(leftInorder, leftPostorder);
        root->right = doit(rightInorder, rightPostorder);
        return root;
    }

};

不开辟数组精简版。

//C#
public class Solution {
    public TreeNode BuildTree(int[] inorder, int[] postorder) {
       if (inorder.Length == 0 || postorder.Length == 0) return null;
       // 左闭右开的原则
       return doit(inorder, 0, inorder.Length, postorder, 0, postorder.Length);
    }
    public TreeNode doit(int[] inorder, int inBegin, int inEnd, int[] post, int postBegin, int postEnd) {
        if (postBegin == postEnd) return null;
        int rootValue = post[postEnd - 1];
        TreeNode root = new TreeNode(rootValue);

        if (postEnd - postBegin == 1) return root;
        int index;
        for (index = inBegin; index < inEnd; index++) {
            if (inorder[index] == rootValue) break;
        }
        int leftInBegin = inBegin;
        int leftInEnd = index;
        int rightInBegin = index + 1;
        int rightInEnd = inEnd;

        int leftPostBegin = postBegin;
        int leftPostEnd = postBegin + index - inBegin;
        int rightPostBegin = postBegin + index - inBegin;
        int rihgtPostEnd = postEnd - 1;

        root.left = doit(inorder,leftInBegin, leftInEnd, post, leftPostBegin, leftPostEnd);
        root.right = doit(inorder, rightInBegin, rightInEnd,post, rightPostBegin, rihgtPostEnd);
        return root;

    }
}

105.从前序与中序遍历序列构造二叉树

前序和后序的不同就在于后序的最后一个元素作为节点,而前序是第一个元素作为节点,所以切分区间的时候左右中序的区间边界不用动[inBegin, index) [index + 1, inEnd),左右后序区间[postBegin, postBegin + index - inBegin) [postBegin + index - inBegin, end - 1)变成左右前序空间[postBegin + 1, postBegin + 1 + index - inBegin) [postBegin + 1 + index - inBegin, end)即可,注意这时候用数组resize就没法把第一个元素去掉了。

public class Solution {
    public TreeNode BuildTree(int[] preorder, int[] inorder) {
       if (inorder.Length == 0 || preorder.Length == 0) return null;
       // 左闭右开的原则
       return doit(inorder, 0, inorder.Length, preorder, 0, preorder.Length);
    }
    public TreeNode doit(int[] inorder, int inBegin, int inEnd, int[] pre, int preBegin, int preEnd) {
        if (preBegin == preEnd) return null;
        int rootValue = pre[preBegin];
        TreeNode root = new TreeNode(rootValue);

        if (preEnd - preBegin == 1) return root;
        int index;
        for (index = inBegin; index < inEnd; index++) {
            if (inorder[index] == rootValue) break;
        }
        int leftInBegin = inBegin;
        int leftInEnd = index;
        int rightInBegin = index + 1;
        int rightInEnd = inEnd;

        int leftpreBegin = preBegin + 1;
        int leftpreEnd = preBegin + 1 + index - inBegin;
        int rightpreBegin = preBegin + 1 + index - inBegin;
        int rihgtpreEnd = preEnd;

        root.left = doit(inorder,leftInBegin, leftInEnd, pre, leftpreBegin, leftpreEnd);
        root.right = doit(inorder, rightInBegin, rightInEnd,pre, rightpreBegin, rihgtpreEnd);
        return root;

    }
}

C++ 使用数组,不用边界存,这种有多个参数的一定看清楚参数顺序,别但层递归的时候写反了,这两题都在这吃亏了。


class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (preorder.size() == 0|| inorder.size() == 0) return nullptr;
        return doit(preorder, inorder);
    }

    TreeNode* doit(vector<int> preorder, vector<int> inorder) {
        if (preorder.size() == 0) return nullptr;
        TreeNode* cur = new TreeNode(preorder[0]);
        cout<<cur->val<<endl;
        if (preorder.size() == 1) return cur;
        int index;
        for (index = 0; index < inorder.size(); index++) {
            if (inorder[index] == preorder[0]) break;
        }
        vector<int> leftIn(inorder.begin(), inorder.begin() + index);
        vector<int> rightIn(inorder.begin() + index + 1, inorder.end());
        vector<int> leftPre(preorder.begin() + 1, preorder.begin() + 1+ leftIn.size());
        vector<int> rightPre(preorder.begin() + 1 + leftIn.size(), preorder.end());
        
        cur->left = doit(leftPre, leftIn);
        cur->right = doit(rightPre, rightIn);

        return cur;

    }
};

二刷看一下C#的数组拆分操作。