【LeetCode】Biweekly Contest 12:Tree Diameter 求无向树的最长路径&求树的半径

一、概述

输入一个无向树,输出其距离最远的两个叶子节点之间的距离。

这题做的我要哭了。做到最后也没做出。十分心酸。然后看了大佬们的代码,发现是我最开始的出发点就错了。

二、分析

1、我的做题经过

最开始想了一会,然后决定DFS。怎么DFS呢?由于输入的是两个端点,我就把输入转化成二维数组。

把无向树转化为无向图。暴力对每一个节点DFS,报TLE。

然后优化,怎么优化?不再对每个节点DFS,只对叶子节点DFS。多过了几个testcase。还是TLE。不知道怎么优化了。

遂换算法。

我就想,能不能用二维数组储存每个节点到其他所有节点的当前距离呢?我遍历输入的vector,有一个[i,j],那么表示节点i到节点j的距离为1,令M[i][j]=1,然后看所有已知节点,若其可以连接到i,那么就可以连接到j,且连接到j的距离是连接到i的距离加一。好像可以。只需要遍历一次输入向量就好了。沾沾自喜。只需要遍历一次输入,循环体中对每个节点遍历一次,大概是O(m*n)的复杂度。这里m是边的数量,n是节点序号的最大值。

喜个鸡儿。先不说m会很大,n也会很大——这个M是一个巨大的稀疏矩阵暂且不表,单看M的大小就吃不消。最开始设置M为1000*1000。过到第15个testcase,有序号为1020的了。我往上加到5000,过到19个,再加就爆栈。怎么办?不能用了。

改吧。把稀疏数组改成unordered_map,压缩了所有的不相连的节点。又做。真的蠢啊同志们,想一想,都是一个无向图了,怎么可能是稀疏矩阵呢?肯定都是有值的。根本压缩不了不说,map的遍历还不能用下标,只能用迭代器。这天杀的迭代器慢到爆炸,TLE来的比之前还早。我就崩溃了。

既然知道压缩不了,那么我不用int,用short会不会好一点?没用,7000*7000还是会爆栈——这方法就不行。

我不死心,既然建在栈上我爆栈,我建堆上不行么,我new一个二维short矩阵(多卑微啊,不心疼么),然后memset为-1。

行吗,不行,这次不爆栈了,tm又TLE,在堆上操作没有栈上快啊。哭了。

时空复杂度不能两全啊。我自己这俩憨憨算法都报废了。根本没脸贴上来,就记录一下思路就好了。

其实思路也是垃圾思路,每个人都能想得出来,但是我这一路撞了不知多少南墙,到最后还是认了。气死了。

2、正常思路

正常思路是两次DFS。怎么两次DFS就能做出来呢?第一次随便找一个节点为根节点DFS,找到距离它最远的节点,这个节点一定是最长路径中的一个端点,第二次以这个端点进行DFS,就能找到最长路径了。

wdnmd,我就没想到还能这么做。凭什么啊,按他这么说,最长路径的端点我设置为a和b,对于剩余所有端点,距离其最远的端点不是a就是b咯。是这样的。为什么啊?我不服啊,遍历所有叶子节点到你这里遍历两次就行了,我这岂不是蠢上了天?还真就蠢上了天。

我给你掰扯掰扯:假设a和b是最长的路径的两个端点,那么现在我有一个节点c,距离c最远的节点既不是a也不是b,而是x。那么现在c的位置有两种情况:①、c在路径a到b上;②、c不在路径a到b上。

先看第一种情况,那么路径ca和cb组成了最长路径,ca和cb是c的两条子树,c是它们的最近公共祖先。现在有cx距离c最远,那么我完全可以用cx代替ca或cb中较短的那个,那么会生成一条更长的路径,这与a和b是最长的路径上的两个端点矛盾。

再看第二种情况,若c不在路径a到b上,那么c一定经过一定的路径可以到达路径a到b上某一点m,即m是它们的最近公共祖先。现在有cx距离c最远,那就有d(c,x)>d(c,m)+d(m,a);d(c,x)>d(c,m)+d(m,b);假设d(m,a)较长,那么根据前提条件,我们有d(m,a)+d(m,b)最大,现在看d(m,a)+d(m,c)+d(c,x),已知d(c,x)>d(c,m)+d(m,b),那么d(m,a)+d(m,c)+d(c,x)>d(m,a)+d(m,c)+d(c,m)+d(m,b)>d(m,a)+d(m,b),又与a和b是最长的路径上的两个端点矛盾。

因此证明了原命题是对的。我tm怎么可能想得出来。

所以事情就很简单了:

class Solution {
    int res=0;
    int best=0;
    void DFS(vector<vector<int> >& M,int m,int pre,int cur)
    {
        if(cur>res)
        {
            res=cur;
            best=m;
        }
        for(int i=0;i<M[m].size();i++)
        {
            if(M[m][i]==pre)
                continue;
            else
                DFS(M,M[m][i],m,cur+1);
        }
    }
public:
    int treeDiameter(vector<vector<int>>& edges) {
        vector<vector<int> > M;
        M.resize(edges.size()+1);
        for(int i=0;i<edges.size();++i)
        {
            int m=edges[i][0];
            int n=edges[i][1];
            M[m].push_back(n);
            M[n].push_back(m);
        }
        DFS(M,0,0,0);
        DFS(M,best,best,0);
        return res;
    }
};

注意题中有这样几个假设没有明说:节点序号从0开始,然后1,2,3,不会出现没有99却有100的情况。因此我们resize可以直接用edges的最大值,而不要用10010之类的,还是会爆栈。之后按邻接表来做就可以了。

三、总结

不要钻牛角尖!!!自己的算法如果优化好久都优化不出,说明肯定是算法的问题,不要去纠结数据结构用的对不对了。简而言之,一道题你想半小时都没想出解法,那你一小时也大概率想不出,与其耗费这么久不如直接去看答案。不然即使做出来了也得不偿失。

哲学。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章