非递归遍历

非递归前序遍历

迭代遍历都是用栈做辅助,存放父节点以便遍历当前子树后,再去遍历当前子树的兄弟子树。思路较清晰,只需注意循环的判断条件即可。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode*> st;
        TreeNode* now = root;
        while(now || !st.empty()) {
            if(!now) {
                now = st.top()->right;
                st.pop();
                continue;
            }
            ans.push_back(now->val);
            st.push(now);
            now = now -> left;
        }
        return ans;
    }
};

非递归中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode*> st;
        TreeNode* now = root;
        while(!st.empty() || now) {
            if(!now) {
                now = st.top();
                st.pop();
                ans.push_back(now->val);
                now = now -> right;
                continue;              
            }
            st.push(now);
            now = now -> left;
        }
        return ans;
    }
};

非递归后序遍历

后序遍历比较特殊,根结点需要在左右子树之后访问,沿用前序和中序的方法时,就无法判断究竟这个空结点是左子树的空结点还是右子树的空结点。因此我们就需要再多加一层判断,观察发现,我们在做后序遍历时,每次访问结点无外乎两种情况——该结点是叶子结点,或该结点的右结点刚刚被访问过。叶子结点很好判断,因此,我们只需要再多设置一个结点,记录上一个被访问过的结点,只要满足这两个条件之一,我们就可以输出当前结点的值。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode* > st;
        TreeNode* now = root, *last = nullptr;
        while(!st.empty() || now) {
            if(!now) {
                now = st.top();
                // 可以访问结点
                if(now->right == nullptr || now->right == last) {
                    ans.push_back(now->val);
                    last = now;
                    st.pop();
                    now = nullptr;
                }
                else {
                    now = now -> right;
                }
                continue;
            }
            st.push(now);
            now = now -> left;
        }
        return ans;
    }
};

二叉树展开为链表

题目描述

题目链接:https://leetcode-cn.com/probl...

给你二叉树的根结点 root ,请你将它展开为一个单链表:

展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。

后序遍历原地修改法

思路:由于顺序必须按照先序遍历,为了方便进行结点的记录和左右子树链接的修改,可以用后序遍历的方法,如果用先序遍历,则当前结点的下一结点较难判断,将当前结点传至下一次递归中的做法也很麻烦。而后续遍历中,只需用一个last结点记录当前结点的下一个结点,按后序遍历记录,可以使得代码很清晰。

TreeNode* last = nullptr;
    void flatten(TreeNode* root) {
        if(!root)return;
        flatten(root->right);
        flatten(root->left);
        root->right = last;
        root->left = nullptr;
        last = root;
    }                                                                                                                                

从根结点出发的遍历

求根节点到叶节点数字之和

题目描述

https://leetcode-cn.com/probl...
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:

例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。

叶节点 是指没有子节点的节点。

思路

这题其实思路很简单,不过这里记录一个执行速度很快的解法,即直接用一个string记录过程中碰到的数字,到了叶子结点用c_str()转换成数字,这样不必去遍历什么了。其中需要注意一下string也是有pop_back()这个函数的。

class Solution {
public:
    string s = "";
    int ans = 0;
    void dfs(TreeNode* root) {
        s += '0' + root->val;
        if(!root->left && !root->right) {
            ans += atoi(s.c_str());
            s.pop_back();
            return;
        }
        if(root->left) dfs(root->left);
        if(root->right) dfs(root->right);
        s.pop_back();
        return;
    }
    int sumNumbers(TreeNode* root) {
        dfs(root);
        return ans;
    }
};

不从根结点出发的遍历

二叉树最大路径和

题目描述

题目链接:https://leetcode-cn.com/probl...
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

示例1:
image.png

输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

递归法思路

二叉树的题,其中一个较常规的思路就是递归法。尤其是这种算最大最小值的题,非常适合用递归来解决。但这题特殊的一个点在于:路径并不仅限于从根到子节点,而可以从一个节点到另一个节点。初看起来非常复杂,用递归思路又不可行了,但当你画一棵较为复杂的树,自己比划比划其中可能的路径时就会发现,事实上可能存在的路径并没有那么多。这题最困扰人的一点就是路径可以从一个节点的左子树走到该结点的右子树,因此使人感觉可能性太多,极难穷举。但自己画过一遍后可以发现,一旦你所画的路径中同时包含了一个节点的左子树和右子树及其本身,这条路径就没法回溯到上一层了;如果想要回溯到上一层,就只能是走其中一个子树。
看清了这一点,就可以得出一个结论:
对于任意一个节点, 如果最大和路径包含该节点, 那么只可能是两种情况:

  1. 其左右子树中所构成的和路径值较大的那个加上该节点的值后向父节点回溯构成最大路径
  2. 左右子树都在最大路径中, 加上该节点的值构成了最终的最大路径

这样,递归法就很好做了:

class Solution {
public:
    int ans = INT_MIN;
    int countSum(TreeNode* root) {
        if(!root) return 0;
        int leftMax = max(countSum(root -> left),0);
        int rightMax = max(countSum(root -> right),0);
        ans = max(ans, leftMax+rightMax+root->val);
        return max(leftMax,rightMax) + root->val;
    }
    int maxPathSum(TreeNode* root) {
        countSum(root);
         return ans;
    }
};

二叉树的直径

题目描述

https://leetcode-cn.com/probl...

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
注意:两结点之间的路径长度是以它们之间边的数目表示。

思路

本质上与上一题是一样的,只是所要求的值不同,是上面一题的简化版。

class Solution {
public:
    int ans = 0;
    int countLen(TreeNode* root) {
        if(!root) return 0;
        int LeftLen = countLen(root->left);
        int RightLen = countLen(root->right);
        ans = max(ans, LeftLen + RightLen + 1);
        return max(LeftLen ,RightLen) + 1;
    }
    int diameterOfBinaryTree(TreeNode* root) {
        countLen(root);
        return ans - 1;
    }
};

二叉树的路径(非根结点出发)

题目描述

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

思路

由于路径不需要从根结点开始,也不需要在叶子结点结束,因此判断的时候极难掌握结点的取舍。最直观的想法是遍历到叶子结点,记录过程中的所有结点值,在最后一层的时候做暴力求解,但时间复杂度爆炸,最坏的情况下时间复杂度将会达到O(n³)。又想到之前求和等于目标数之类的题的时候用到了hashmap,想着是不是可以记录所有阶段的路径和,在每个节点判断时,用key为目标数-前缀和的方法去map中找合适的路径,但这种方法会导致所匹配的路径与当前结点并不构成一条完整通路,方法2还是失败。。无奈看了答案。

答案主要分两种思路:递归法偏暴力,略;第二种方法为前缀和法,思路与自己想的有点相通,但更为巧妙:在每个结点存储到map的时候,存的key是从根结点到当前结点的路径和(即前缀和),在每个结点做判断时,以目标数-前缀和为key到map中寻找路径数。这样做可以完美覆盖到仅有的两类目标路径:

  1. 当从根结点到当前结点的路径和正好为目标数,此时目标数-前缀和=0(注意,需要在map初始化时就加入map[0]=1),count+1
  2. 当从非根结点(假设为结点q)到当前结点的路径为目标数,此时这条路径可以看成 根结点到当前结点 - 根结点到q的前一结点(假设为结点m)。根结点到m这一路径已经存储到了map中,这个时候的前缀和-目标数 = 根结点到m的路径和,因此res+1。
class Solution {
public:
    unordered_map<int, int> count;
    int pathSum(TreeNode* root, int sum) {
        count[0] = 1;
        return helper(root, sum, 0);
    }
    int helper(TreeNode* root, int sum, int prefix_sum) {
        if (!root) return 0;
        int res = 0;
        prefix_sum += root->val;
        res += count[prefix_sum - sum];
        count[prefix_sum]++;
        res += helper(root->left, sum, prefix_sum) + helper(root->right, sum, prefix_sum);
        count[prefix_sum]--;
        return res;
    }
};

用层序遍历解决的问题

对称二叉树

题目描述

https://leetcode-cn.com/probl...

迭代法

即用层序遍历,对于两个结点,每次反着入队,依次检查弹出两个结点是否相同即可。注意要判断一个空一个不空的情况。

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        queue<TreeNode* > q;
        q.push(root);
        q.push(root);
        TreeNode* node1, *node2;
        while(!q.empty()) {
            node1 = q.front();
            q.pop();
            node2 = q.front();
            q.pop();
            if(!node1 && !node2) continue;
            if(!node1 || !node2) return false;
            if(node1->val != node2->val) return false;
            q.push(node1->left);
            q.push(node2->right);
            q.push(node1->right);
            q.push(node2->left);
        }
        return true;
    }
};

递归法

递归法原理类似,但更好理解一些。空间上略胜,不过时间上略差

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        return isMirror(root, root);
    }
    bool isMirror(TreeNode* node1, TreeNode* node2) {
        if(!node1 && !node2) return true;
        if(!node1 || !node2) return false;
        if(node1->val != node2->val) return false;
        return isMirror(node1->left, node2->right) && isMirror(node1->right, node2->left);
    }
};

SalvationN
1 声望0 粉丝