一、概述
輸入一個字符串,字符串中包含“/ . a~z”,組成一個文件路徑。將其化爲最簡路徑。
有以下幾條要注意
有“/..”時,相當於返回上一層,它前面的一層要刪掉;
有“/.”時,相當於在本層,沒卵用,可以刪掉;
多個/和一個一樣,可以壓縮。
看上去很簡單,實際很麻煩,有許多邊界條件。
二、分析
1、我的方法
有邊界條件沒找出,沒AC,而且代碼十分醜陋。實在是懶得再去debug了。
class Solution {
public:
string simplifyPath(string path) {
//return "";
if(path=="")
return "";
//if(path=="/")
//return "/";
string pure;
for(int i=0;i<path.size();i++)
{
if(path[i]=='/')
{
pure+=path[i];
while(i<path.size()&&path[i]=='/')
i++;
i--;
}
else
pure+=path[i];
}
cout<<pure<<'\n';
if(pure=="/")
return "/";
if(pure[pure.size()-1]=='/')
pure.erase(pure.size()-1);
string pure_real0,pure_real;
for(int i=0;i<pure.size()-2;i++)
{
if(pure[i]=='/'&&pure[i+1]=='.'&&pure[i+2]=='/')
{
i++;
}
else
pure_real0+=pure[i];
}
if(!((pure[pure.size()-2]=='/'&&pure[pure.size()-1]=='.')))
pure_real0=pure_real0+pure[pure.size()-2]+pure[pure.size()-1];
if(pure_real0[pure_real0.size()-1]=='/')
pure_real0.erase(pure_real0.size()-1);
cout<<pure_real0<<'\n';
for(int i=0;i<pure_real0.size()-3;i++)
{
if(i<pure_real0.size()-3&&pure_real0[i]=='/'
&&pure_real0[i+1]=='.'&&pure_real0[i+2]=='.'&&pure_real0[i+3]=='/')
{
i=i+2;
pure_real+='!';
}
else
pure_real+=pure_real0[i];
}
if(pure_real[pure_real.size()-1]!='!')
if(pure_real0[pure_real0.size()-3]=='/'&&pure_real0[pure_real0.size()-2]=='.'
&&pure_real0[pure_real0.size()-1]=='.')
pure_real+='!';
else
pure_real=pure_real+pure_real0[pure_real0.size()-3]
+pure_real0[pure_real0.size()-2]+pure_real0[pure_real0.size()-1];
else
{
}
if(pure_real[pure_real.size()-1]=='/')
pure_real.erase(pure_real.size()-1);
cout<<pure_real<<'\n';
for(int i=0;i<pure_real.size();i++)
{
if(pure_real[i]=='!')
{
int j=i-1;
while(j>=0&&pure_real[j]!='/')
{
pure_real[j]='#';
j--;
}
if(j>=0&&pure_real[j]=='/')
pure_real[j]='#';
}
}
cout<<pure_real<<'\n';
string res;
for(int i=0;i<pure_real.size();i++)
{
if(pure_real[i]!='#'&&pure_real[i]!='!')
res+=pure_real[i];
}
if(res.size()==0)
return "/";
if(res[0]!='/')
res='/'+res;
return res;
}
};
中心思想就是先對“/”進行壓縮,把多個重複的“/”壓縮成一個。然後對“/.”進行處理,注意只有當“/./”的時候纔可以把“/.”刪除。最後對“/..”進行處理。爲了便於處理,我先將“/../”變爲“*/”,然後再次遍歷,遇到*就向前刪除直到遇到/,這實在太麻煩了。
最主要的細節處理不當都存在字符串的末尾。由於我判斷“/.”實際上要判斷/./”,要判斷“/..”實際上是判斷“/../”,那麼最後幾個字符就需要單拿出來判斷,這很容易出現沒有料到的情況,總是如同打地鼠一樣,按下葫蘆起了瓢,就很煩躁。debug了半天搞得我道心不穩,而且從這裏也什麼都學不到——然後就去看討論區了。
2、較好的代碼
該代碼中用了我平時很少使用的getline函數和棧結構,從而很容易的完成了該任務。
getline函數最多有三個參數:第一個參數是istream&類型,稱爲輸入數據流,簡而言之就是輸入的字符串;第二個參數是string&類型,輸入數據流會把輸入寫入這個參數;第三個參數是char,截斷字符,函數在輸入數據流中遇到該字符就停止寫入。
要想使用該函數,我們要對輸入的path做一點變化:題目中輸入的是string,而getline要的是istream&,那麼我們要將string變爲istream,怎麼變呢?如下:
stringstream ss(path);
很簡單,這樣ss就是path的istream類型了。
然後注意一點,如果getline中輸入流中有多個截斷字符,那麼循環調用getline時,效果就是每次把兩個截斷字符中間的字符串賦值給輸入,截斷字符本身不會被寫入,同時截斷字符在每讀一次之後都會變短,賭讀一次就少一點。
那麼好了,我們把“/”作爲截斷字符,那麼一次又一次調用,就相當於把輸入流分割成了一個一個小塊,然後怎麼處理呢?
對於具有回溯特性的問題,使用棧是一個好選擇。什麼叫回溯特性呢?
就是我發現了某一個條件,該條件需要“從當前向前追溯,直到滿足另一個條件”,類似本題中的遇到“/..”就刪除前一層,這就是回溯特性。那麼如何實現呢?
建一個元素爲字符串的棧,判斷每次讀入的數據,要是“.”就不壓入,要是“..”就彈出一個,要都不是就壓入。注意彈出前判斷棧是否爲空。
這樣就很簡單。當輸入流被讀完,棧中剩下的就是最後要輸出的元素。然後依次彈出,加入“/”即可。
代碼如下:
class Solution {
public:
string simplifyPath(string path) {
string result="", token;
stringstream ss(path);
vector<string> tokens;
while(getline(ss, token, '/')){
if(token=="." || token=="") continue;
else if(token==".."){
if(tokens.size()!=0) tokens.pop_back();
}
else tokens.push_back(token);
}
if(tokens.size()==0) return "/";
for(int i=0; i<tokens.size(); ++i)
result=result+'/'+tokens[i];
return result;
}
};
三、總結
憨憨自閉做法不可取。應用恰當的函數和數據結構,會使得問題簡單許多許多。