leetcode之二叉树类之路径和系列-----112/113/124/257/437 path sum(牵扯附加OJ572和OJ100, 子树和子拓扑)

OJ112:对路径定义为:从root到leaf的path,求路径和为target的路径是否存在

OJ113:对路径定义为:从root到leaf的path,求路径和为target的路径都有哪些

OJ437:对路径定义为:任意子树(含单节点)形成的路径,求路径和为target的路径有多少个

OJ124:对路径定义为:任意子树(含单节点)形成的路径,求路径和最大是多少

OJ257:对路径定义为:从root到leaf的path,求全部的路径


OJ112和OJ113和OJ257为一类,是单一递归可以解决的问题,并且都是需要做leaf才能够确认是否符合题目需求的情况,所以均用先序遍历;

OJ437和OJ124属于二叉树中的"子树类"问题,其中OJ437需要多层次递归来解决(注意OJ572也是"子树类"问题);


OJ112:只需要返回是否存在,首先路径已经被定义为从root到leaf,那么路径和必须在leaf才能做和target是否相同的判断,那么就做先序遍历,判断当前节点是否为leaf节点,非leaf的上层节点,返回左右子树的返回值的或;

OJ112代码:

class Solution {
public:
    bool helper (TreeNode *cur, int sum) {
        if (cur) {
            if (!cur->left && !cur->right) {
                if (sum == cur->val) {
                    return true;
                } else {
                    return false;
                }
            } else {
                bool l = false, r = false;
                if (cur->left) {
                    l = helper(cur->left, sum - cur->val);
                }
                if (cur->right) {
                    r = helper(cur->right, sum - cur->val);
                }
                return l || r;
            }
        }
    }
    
    bool hasPathSum(TreeNode* root, int sum) {
        if (!root) {
            return false;
        } else if (!root->left && !root->right) {
            return (root->val == sum)?true:false;
        }
        
        return helper(root, sum);
    }
};
OJ113和OJ112唯一差别是,无需返回是否存在,仅需要在leaf节点确认路径和为target时,把这个路径记下来,代码稍作修改即可

OJ113代码:

class Solution {
public:
    void helper (TreeNode *cur, int sum, vector<int> v, vector<vector<int>> &res) {
        if (cur) {
            v.push_back(cur->val);
            if (!cur->left && !cur->right) {
                if (sum == cur->val) {
                    res.push_back(v);
                }
            } else {
                if (cur->left) {
                    helper(cur->left, sum - cur->val, v, res);
                }
                if (cur->right) {
                    helper(cur->right, sum - cur->val, v, res);
                }
            }
        }
    }
    
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        vector<vector<int>> res;
        if (!root) {
            return res;
        }
        
        vector<int> v;
        helper(root, sum, v, res);
        return res;
    }
};

OJ257,打印从root到每一个leaf的全部路径,同样需要做leaf处才能够得到最终结果,这里的最终结果就是路径,所以和OJ112、OJ113一样也是先序遍历,当判断是leaf节点时,记录当前已走过的路径

OJ257代码:

class Solution {
public:
    void helper (TreeNode *cur, string s, vector<string> &res) {
        if (cur) {
            if (!cur->left && !cur->right) {
                s += to_string(cur->val);
                res.push_back(s);
            } else {
                s += to_string(cur->val);
                s += "->";
                helper(cur->left, s, res);
                helper(cur->right, s, res);
            }
        }
    }
    
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;
        if (!root) {
            return res;
        }
        
        helper(root, "", res);
        return res;
    }
};

OJ124:对路径定义为从一个任意点到另一个任意点的路径,求路径和最大的路径,而且没有路径值为正负零的约束

这种情况下在任何一个节点都有可能出现路径最大值,这时需要的是每个节点都要计算比较更新最大路径和。一般如果是到leaf节点才能见分晓做判断的都用先序遍历,每一个节点都需要做判断的用后序遍历,如本题。

核心思路点:

1、每一个节点处,都要做最大路径和的判断,判断来源包括:当前节点自己(cur->val)、当前节点左子节点到右子节点的路径和、当前节点到左子节点的路径和、当前节点到右子节点的路径和,取四者最大值和最大路径和max比较,如大于max则更像max;

2、每个节点要返回给上层节点当前层的"到本节点的最大路径和",也就是剔除掉当前节点左子节点到右子节点的路径和,因为当前节点是上层节点的左或右子节点,上层节点需要用本节点的最大路径和作为它的计算1的来源,这个是本题的核心,一定想清楚。即使用当前节点自己(cur->val)、当前节点到左子节点的路径和、当前节点到右子节点的路径和,取三者最大值,作为给上一层的返回值;除空节点返回0外,其他节点均返回计算结果给上一层;

最终结果就是不断更新得到的最大路径和max

OJ124代码:

class Solution {
public:
    int helper (TreeNode *cur, int sum, int &max) {
        if (cur) {
            int left = helper(cur->left, sum + cur->val, max);
            int right = helper(cur->right, sum + cur->val, max);
            
            if (max < cur->val) {
                max = cur->val;
            }
            if (max < (cur->val + left)) {
                max = cur->val + left;
            }
            if (max < (cur->val + right)) {
                max = cur->val + right;
            }
            if (max < (cur->val + left + right)) {
                max = cur->val + left + right;
            }
            
            return std::max(cur->val, std::max(cur->val + left, cur->val + right));
        } else {
            return 0;
        }
    }
    
    int maxPathSum(TreeNode* root) {
        if (!root) {
            return 0;
        }
        
        int max = INT_MIN;
        helper(root, 0, max);
        return max;
    }
};

OJ437:路径被定义为任意一个节点到另一个任意节点,也可以是单节点构成一个路径,找出路径和为target的个数

典型的子树类问题,子树类问题典型的问题描述是:每一个"半截路径"即不是从root开始到leaf结尾的路径,而是任意节点A到任意节点B,A甚至可以和B是同一个节点;而具体对于本题的要求是:节点路径和为target的有多少个。

子树类问题的解决思维是:每一个节点都是root,每一个节点也都是leaf,

而更具体的解决框架是:当前节点、左子树、右子树同时发起递归,递归内部把输入的节点像根节点一样的处理


如root节点为A,左子节点为B右子节点为C,则同时发起A、B、C的递归,B、C进而发起它们各自的左右子节点的递归,即"每个节点都作为root"做处理;然后,递归处理函数中,也要做"每个节点都是leaf"的处理,对于本题就需要对每个路径(含单节点构成的路径)与target做比较,判断是否已经符合要求,同时还要判断加入每层节点后的路径是否又符合要求;

以一个极端例子,某子树节点为10,target就是10,该节点底下N多子节点,value都是0,那么它和全部子节点组成的各种路径都符合要求;

OJ437代码:

class Solution {
public:
    int helper (TreeNode *cur, int c, const int sum) {
        if (cur) {
            c += cur->val;
            return (c == sum) + helper(cur->left, c, sum) + helper(cur->right, c, sum);
        } else {
            return 0;
        }
    }
    
    int pathSum(TreeNode* root, int sum) {
        int res = 0;
        if (!root) {
            return 0;
        }
        
        res = helper(root, 0, sum) + pathSum(root->left, sum) + pathSum(root->right, sum);
        return res;
    }
};

牵扯到子树,看看另一个子树题,OJ572,判断一个二叉树A是否是另一个二叉树B的子树

注意:子树要求完全一致,不仅节点value和拓扑必须相同,而且不可有不同的子节点,如B的A部分,底下还有其他节点,那不行

先看下"两个树是否为相同的树"(OJ100),看如何做两个二叉树是否相同的判断方法

OJ100代码:

class Solution {
public:
    bool helper (TreeNode *p, TreeNode *q) {
        if (!p && !q) {
            return true;
        } else if (!p || !q) {
            return false;
        } else {
            if (p->val == q->val) {
                bool l = helper(p->left, q->left);
                if (l) {
                    return helper(p->right, q->right);
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
    }
    
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if (!p && !q) {
            return true;
        } else if (!p || !q) {
            return false;
        }
        
        return helper(p, q);
    }
};

必须节点个数、拓扑、节点value完全相同才行,对于OJ572,如:

B是如下:

     3
    / \
   4   5
  / \
 1   2
    /
   0
A是如下:

   4
  / \
 1   2
A不是B的子树;

但如果B是如下:

     3
    / \
   4   5
  / \
 1   2
A是如下:

   4 
  / \
 1   2
这样A就是B的子树;


所以OJ572是OJ100的扩展,OJ100是A和B是否相同,OJ572是A是否包含有B,子树类问题的典型解决方式:同时发起递归,递归处理函数内全部当作root一样处理

步骤:

1、当前节点、左子树、右子树同时发起递归处理;进而左、右子树下的每个节点都发起递归

2、递归处理函数和OJ100的完全一样

3、主函数只要发现符合要求的子树即返回true结束,没发现就一直递归处理下去


OJ572代码:

class Solution {
public:
    bool helper (TreeNode *s, TreeNode *t) {
        if (!s && !t) {
            return true;
        } else if (!s || !t) {
            return false;
        } else {
            if (s->val == t->val) {
                return helper(s->left, t->left) && helper(s->right, t->right);
            } else {
                return false;
            }
        }
    }
    
    bool isSubtree(TreeNode* s, TreeNode* t) {
        if (!s && !t) {
            return true;
        } else if (!s || !t) {
            return false;
        }
        
        if (s->val == t->val) {
            if (helper(s, t)) {
                return true;
            }
        }
        
        return isSubtree(s->left, t) || isSubtree(s->right, t);
    }
};


从代码看出,主函数如果判断当前节点的value和待比较树的root的value都不一样,那么就已经不符合了,没必要递归处理了,减少一些陷入递归的case。


从这道题最重要需要掌握的是"子树类"问题的解决思维和解决模型;

另外,可以联想下,"比较A与B,或A与B的子树,不要拓扑完全相同,只要B也有A的结构,也就是说B的A部分还有其他子树也可以"这样的题怎么办,很显然,放宽了条件,不必一定是子树,也就不需要"!p && !q时返回true、!p || !q时返回false"的条件,取而代之的是需要比较A的左右和B的左右只要同时存在且value相同即可;所以,如果是A与B比较,那么形同OJ100的解决方法,如果是A与B的各种子树比较,就形同OJ572的解决方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值