散列表的树中距离之和

树中距离之和:
https://leetcode-cn.com/problems/sum-of-distances-in-tree/

详情题解请看官方题解,而我只不过是将代码与之对应。
说来读题读懂都是一个问题,实际上还是理解能力的限制在作天花板。

树形动态规划其实还是很容易看出的。相互的递推关系也不过是交换的问题,不过最开始我想到的交换,最终写代码的时候却没有加上。还是工具的运用没有到如火纯情的地步。

这是我花费了一个早晨看懂的代码。
总之是这样,其实思路早就明确了,但是总是代码还是看不懂。所以算法越精妙的代码,可读性真的就是越差。这是一个无法回避的事实。那么我有感而发。
越是聪明的头脑,可读性也同样是越差的。
所以,敬佩那些科学伟人们。同样也为他们一生的孤独而感到遗憾。

其实最开始还有一个疑问,我不明白时间复杂度是怎么到了O(n)。因为我看到他遍历了数组,vector< int >后来发现,这是一个散列表。题目所给也是一个散列表。那么其实就可以想一下如何去用树的存储结构去做这个题目了。

作者想的很全面吗?是否还有更加可以优化的地方呢?

我本来觉得可以把所谓的散列变成有向图的形式,但是实际上dfs2中的交换次序使得我的想法变得不可行。考虑到子节点和父节点的交换,其实其相互的交换只是变了一个散列值,而若后来进行修改的话,涉及到的操作可能更加复杂,真的可能不如想办法去continue,而不是去修改散列。保证数据正确的情况下,才能去谈代码算法。

class Solution {
    
    
public:
    vector<int> ans, sz, dp;
    vector<vector<int>> graph;
    void dfs(int u, int f) {
    
    
        sz[u] = 1;//最底层的节点是1
        dp[u] = 0;//最底层的节点距离是0
        for (auto& v: graph[u]) {
    
    //在每个根的子节点里面寻找距离
            if (v == f) {
    
    //碰到计算过的就跳,即只计算树的根和他的的子节点
                continue;
            }
            dfs(v, u);//计算u和他的节点v之间
            dp[u] += dp[v] + sz[v];//计算根到其子节点的距离
            sz[u] += sz[v];//计算节点数量
        }
    }

    void dfs2(int u, int f) {
    
    
        ans[u] = dp[u];//首先确定“相对”根节点的情况
        for (auto& v: graph[u]) {
    
    //然后接着计算此根节点下属的所有子节点的情况
            if (v == f) {
    
    
                continue;
            }//不允许计算根节点,该散列表中只有一个节点非该节点的子节点,即在进行根节点push的时候同时所加上的子节点中的根节点
            int pu = dp[u], pv = dp[v];//临时替代
            int su = sz[u], sv = sz[v];//临时替代
            
            dp[u] -= dp[v] + sz[v];//此时v为节点,则原本u所对应的子节点中没有了v,所以其dp值将减去v的贡献
            sz[u] -= sz[v];//其树的节点数量中将减去v的贡献
            dp[v] += dp[u] + sz[u];//v变成根节点之后,加上u的贡献
            sz[v] += sz[u];//节点集合加上u的贡献
            //由于其他的子节点的下属节点未发生改变,则应当没有变化
            dfs2(v, u);//进行v的子节点的答案计算

            dp[u] = pu, dp[v] = pv;//每次计算之后应当将原本的dp复原,因为此时需要恢复至最开始的数树的状态
            sz[u] = su, sz[v] = sv;//节点集合也应当回到最开始的状态
        }
    }

    vector<int> sumOfDistancesInTree(int N, vector<vector<int>>& edges){
    
    
        ans.resize(N, 0);
        sz.z(N, 0);
        dp.resize(N, 0);
        graph.resize(N, {
    
    });
        for (auto& edge: edges) {
    
    
            int u = edge[0], v = edge[1];
            graph[u].emplace_back(v);
            graph[v].emplace_back(u);
        }
        dfs(0, -1);
        dfs2(0, -1);
        return ans;
    }
};

本题有一个天然顺序是需要先计算子节点才能计算父节点。这应当体现在所有的细节里面,所以在dfs2中,dp和sz的动态规划顺序也应当是按照此顺序进行的。

猜你喜欢

转载自blog.csdn.net/weixin_47741017/article/details/108936725