【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之類的,還是會爆棧。之後按鄰接表來做就可以了。

三、總結

不要鑽牛角尖!!!自己的算法如果優化好久都優化不出,說明肯定是算法的問題,不要去糾結數據結構用的對不對了。簡而言之,一道題你想半小時都沒想出解法,那你一小時也大概率想不出,與其耗費這麼久不如直接去看答案。不然即使做出來了也得不償失。

哲學。

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