1. 程式人生 > >Simplify Path 4ms Java Solution

Simplify Path 4ms Java Solution

這是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風格目錄的分隔符)的字串,且該字串不為“.”和“..”。因為“.”代表當前目錄,可省略;而“..”表上級目錄,可以進一步簡化,即“/#/#/..”可以簡化為“/#/”,這就需要用到

的性質了。此外,需要注意的一點就是絕對路徑最終的形式總是要以“/”為開頭,否則為無效路徑。

所以,我們演算法可描述如下:

  1. 首先對原始的路徑字串path以“/”為分隔符,將其分為字串陣列。分割的方法可以是遍歷字串path的每個字元並以“/”為分割標誌,也可以用Java中String自帶的方法split()來分割。注意這兩種方法存在效率差異,後面會有說明。

  2. 然後,按順序從左到右對字串陣列進行棧操作,可能的字串有{“”, “.”, “..”, 其他形式}, 其中

    • 若字串為“”或“.”,不做任何處理;
    • 若字串為“..”,則執行出棧 操作(注意,若棧為空,不做任何操作);
    • 否則, 執行壓棧 操作。
  3. 最後,就是還原最終的路徑了,將棧底至棧頂的字串從左到右填進“/#/#/…. /#”格式中的“#”中去。

演算法時間的複雜度為遍歷字串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由於支援正則表示式,所以效率會比較低,呼叫頻率太高將會耗費大量資源。