一、概述
输入一个字符串,字符串中包含“/ . 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;
}
};
三、总结
憨憨自闭做法不可取。应用恰当的函数和数据结构,会使得问题简单许多许多。