这是leetcode中一道在线编程题,原问题链接为
原问题描述很简单,如下:
Given an absolute path for a file (Unix-style), simplify it.
For example,
path = “/home/”, => “/home”
path = “/a/./b/../../c/”, => “/c”
也就是简化Unix风格的文件的绝对路径,该问题难度不大,解决方案主要用栈 来解决,但是这里面存在的一些细节问题,使得快速编写出正确的solution也并不是那么轻松,接下来的任务就是整理这一过程,尽量使该问题的解决方案条理清晰。
首先对问题进行分析,Unix文件的绝对路径最简单的形式是“/#/#/…. /#”,其中#代表任意不包含‘/’(‘/’是Unix风格目录的分隔符)的字符串,且该字符串不为“.”和“..”。因为“.”代表当前目录,可省略;而“..”表上级目录,可以进一步简化,即“/#/#/..”可以简化为“/#/”,这就需要用到栈 的性质了。此外,需要注意的一点就是绝对路径最终的形式总是要以“/”为开头,否则为无效路径。
所以,我们算法可描述如下:
首先对原始的路径字符串path以“/”为分隔符,将其分为字符串数组。分割的方法可以是遍历字符串path的每个字符并以“/”为分割标志,也可以用Java中String自带的方法split()来分割。注意这两种方法存在效率差异,后面会有说明。
然后,按顺序从左到右对字符串数组进行栈操作,可能的字符串有{“”, “.”, “..”, 其他形式}, 其中
- 若字符串为“”或“.”,不做任何处理;
- 若字符串为“..”,则执行出栈 操作(注意,若栈为空,不做任何操作);
- 否则, 执行压栈 操作。
最后,就是还原最终的路径了,将栈底至栈顶的字符串从左到右填进“/#/#/…. /#”格式中的“#”中去。
算法时间的复杂度为遍历字符串path中字符的时间,为O(n),n为path的长度。空间复杂度亦是O(n),因为最终需要返回至多为n长度的简单路径。
下面贴一下我的Java代码:
public class Solution {
public String simplifyPath(String path) {
int len = path.length();
String[] stack = new String[len];
int pop = -1;
int index = 0;
while (index < len) {
if (path.charAt(index) == '/') {
continue;
}
int tail = index;
while (tail < len && path.charAt(tail) != '/')
tail++;
String sub = path.substring(index, tail);
if (sub.equals("..")){
if (pop >= 0) --pop;
} else if (!sub.equals(".")) {
stack[++pop] = sub;
}
index = tail + 1;
}
if(pop == -1) return "/";
StringBuilder simplifiedPath = new StringBuilder();
for (int i = 0; i <= pop; i++) {
simplifiedPath.append("/");
simplifiedPath.append(stack[i]);
}
return simplifiedPath.toString();
}
}
上述代码没有用String自带的方法split()来分割字符串,而是通过直接遍历字符串中字符实现,在leetcode官网其完成252个测试用例用时4ms。
下边与用split()方法分割字符串对比一下性能。其代码实现如下:
public class Solution {
public String simplifyPath(String path) {
int len = path.length();
String[] stack = new String[len];
int pop = -1;
String[] subs = path.split("/");
for (int i = 0; i < subs.length; i++) {
if (subs[i].equals("") || subs[i].equals(".")) continue;
if (subs[i].equals("..")) {
if (pop >= 0) --pop;
}else
stack[++pop] = subs[i];
}
if(pop == -1) return "/";
StringBuilder simplifiedPath = new StringBuilder();
for (int i = 0; i <= pop; i++) {
simplifiedPath.append("/");
simplifiedPath.append(stack[i]);
}
return simplifiedPath.toString();
}
}
在leetcode官网其完成252个测试用例用时8ms,速度要慢上一倍。因此,从性能优化的角度来考虑,除非必要,应该尽量避免使用split,split由于支持正则表达式,所以效率会比较低,调用频率太高将会耗费大量资源。