一、概述
輸入一個無向樹,輸出其距離最遠的兩個葉子節點之間的距離。
這題做的我要哭了。做到最後也沒做出。十分心酸。然後看了大佬們的代碼,發現是我最開始的出發點就錯了。
二、分析
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之類的,還是會爆棧。之後按鄰接表來做就可以了。
三、總結
不要鑽牛角尖!!!自己的算法如果優化好久都優化不出,說明肯定是算法的問題,不要去糾結數據結構用的對不對了。簡而言之,一道題你想半小時都沒想出解法,那你一小時也大概率想不出,與其耗費這麼久不如直接去看答案。不然即使做出來了也得不償失。
哲學。