树形DP算法

首先我们看Leetcode543题二叉树的直径问题

题目描述:

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例 :
给定二叉树

          1
         / \
        2   3
       / \     
      4   5    
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

注意:两结点之间的路径长度是以它们之间边的数目表示。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/diameter-of-binary-tree

下面给出解法:

/**
 * 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:
    int ans = 0;
    int diameterOfBinaryTree(TreeNode* root) 
    {
        TreeDP(root);
        return ans;
    }

    int TreeDP(TreeNode* root)
    {
        if (root == nullptr)
            return -1;
        int left = TreeDP(root->left) + 1;
        int right = TreeDP(root->right) + 1;
        ans = max(ans,left + right);
        return max(left,right);
    }
};

算法介绍:
首先我们先分析题目什么是二叉树的直径,如下图所示,红色部分即是二叉树的直径,可以经过根节点也可以不经过根节点。

 783b9cb4b1964a55ae3bc63ced8bd56a.png

那么我们该如何求二叉树的直径呢,学过数据结构的同学可能知道,二叉树具有着天然的和原问题相似的子问题结构,在求树的深度的时候我们就可将树分为左子树,右子树和根,分别对左右子树递归得到树的深度。

d950b32316074b37bbd2e6fcbb70d61e.png

二叉树的深度 = max(左子树的深度,右子树的深度) + 1

同理,在求二叉树的直径上面我们也可以对直径进行划分,一个树的直径可以由左子树的最大深度 + 右子树的最大深度 + 2(根节点分别到左子树和右子树的边)决定。而要求左右子树的最大深度,我们要分别对左右子树进行递归,递归计算树的深度的同时,我们也可以计算以该子树的直径,并用一个值ans来维护,找到ans的最大值即为我们要求的二叉树直径的最大值。

我们来解析代码递归的核心部分:

int TreeDP(TreeNode* root)
{
   if (root == nullptr)  //当节点不存在时无节点返回-1
      return -1;
   int left = TreeDP(root->left) + 1;//计算左子树的深度 + 根节点到左子树的一条边
   int right = TreeDP(root->right) + 1;//计算右子树的深度 + 根节点到右子树的一条边
   ans = max(ans,left + right);//用ans值维护,现在的直径和计算过的直径对比,寻找最大值
   return max(left,right);//找到左右子树深度的最大值,用于判断当前节点的最大深度
}

接下来我们来看一个进阶的题目,Leetcode2246相邻字符不同的最长路径问题

题目描述:

给你一棵 树(即一个连通、无向、无环图),根节点是节点 0 ,这棵树由编号从 0 到 n - 1 的 n 个节点组成。用下标从 0 开始、长度为 n 的数组 parent 来表示这棵树,其中 parent[i] 是节点 i 的父节点,由于节点 0 是根节点,所以 parent[0] == -1 。

另给你一个字符串 s ,长度也是 n ,其中 s[i] 表示分配给节点 i 的字符。

请你找出路径上任意一对相邻节点都没有分配到相同字符的 最长路径 ,并返回该路径的长度。

示例 1:

输入:parent = [-1,0,0,1,1,2], s = "abacbe"
输出:3
解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3 。
可以证明不存在满足上述条件且比 3 更长的路径。 
示例 2:

输入:parent = [-1,0,0,0], s = "aabc"
输出:3
解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3 。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/longest-path-with-different-adjacent-characters

下面给出解法:

class Solution {
public:
    int ans = 0;
    vector<vector<int>> lists;
    string sl;
    int longestPath(vector<int>& parent, string s) 
    {
        int n = parent.size();
        for (int i = 0;i < n;i++)
        {
            vector<int> list;
            lists.push_back(list);
        }
        for (int i = 1;i < n;i++)
            lists[parent[i]].push_back(i);
        sl = s;
        TreeDP(0);

        return ans + 1;
    }

    int TreeDP(int node)
    {
        int maxlenth = 0;
        for (int i : lists[node])
        {
            int currentlenth = TreeDP(i) + 1;
            if (sl[i] != sl[node])
            {
                ans = max(ans,maxlenth + currentlenth);
                maxlenth = max(maxlenth,currentlenth);
            }
        }
        return maxlenth;
    }
};

题目解析:

本题在求二叉树的直径的基础上改变而来,增加了相邻两个点值不相同的情况。但二叉树只有左子树和右子树,本题树的子树个数是不确定的,所以我们要遍历所有子树,于是我们用一个集合来记录所有节点的子树情况,遍历集合即可。

本题中我们需要找到直径的最大值,则我们需要找到子树的最大值和次最大值相加即可得到直径最大。于是我们用maxlength来维护当前节点的子树的最大值,我们遍历所有的子树的同时更新最大值,并且与当前正在遍历的子树相加,用ans值来维护相加的最大值,即可得到最大直径。

由于本题要求相邻两个节点不相同,所以我们加入sl[i] != sl[node],保证相邻节点不同时在更新ans即可

核心代码解析:

int TreeDP(int node)
{
    int maxlenth = 0;
    for (int i : lists[node])
    {
        int currentlenth = TreeDP(i) + 1;//递归当前树的深度
        if (sl[i] != sl[node])
        {
            ans = max(ans,maxlenth + currentlenth);//维护答案最大值
            maxlenth = max(maxlenth,currentlenth);//更新当前节点树深度的最大值
        }
    }
    return maxlenth;
}

猜你喜欢

转载自blog.csdn.net/m0_53377876/article/details/130371575
今日推荐