[LeetCode] 1233. Remove Sub-Folders from the Filesystem 刪除子文件夾



Given a list of folders folder, return the folders after removing all sub-folders in those folders. You may return the answer in any order.

If a folder[i] is located within another folder[j], it is called a sub-folder of it.

The format of a path is one or more concatenated strings of the form: '/' followed by one or more lowercase English letters.

  • For example, "/leetcode" and "/leetcode/problems" are valid paths while an empty string and "/" are not.

Example 1:

Input: folder = ["/a","/a/b","/c/d","/c/d/e","/c/f"]
Output: ["/a","/c/d","/c/f"]
Explanation: Folders "/a/b/" is a subfolder of "/a" and "/c/d/e" is inside of folder "/c/d" in our filesystem.

Example 2:

Input: folder = ["/a","/a/b/c","/a/b/d"]
Output: ["/a"]
Explanation: Folders "/a/b/c" and "/a/b/d/" will be removed because they are subfolders of "/a".

Example 3:

Input: folder = ["/a/b/c","/a/b/ca","/a/b/d"]
Output: ["/a/b/c","/a/b/ca","/a/b/d"]

Constraints:

  • 1 <= folder.length <= 4 * 104
  • 2 <= folder[i].length <= 100
  • folder[i] contains only lowercase letters and '/'.
  • folder[i] always starts with the character '/'.
  • Each folder name is unique.

這道題給了一堆文件目錄的地址,讓去除其中所有的子目錄,所謂的子目錄,就是在父文件夾下的文件,比如 /home/user 這個文件夾下有個叫 grandyang 的文件夾,則 /home/user/grandyang 就是其的一個子目錄。既然是子目錄,那么路徑中肯定有一部分是和父目錄相同的,所以可以把所有的父目錄都放到一個 HashSet 中,然后遍歷某個要檢驗的路徑的所有前綴,若其在 HashSet 中存在,說明當前是個子目錄。由於路徑越長,其是子目錄的可能性就越大,所以可以按照長度先來排個序,先處理那些長度短的路徑,同時排序也保證了父目錄一定比其子目錄先處理。可以從第三個字符開始遍歷,因為限定了路徑長度至少為2,若遍歷到的字符是 '/',說明前面的部分已經是一個合法的路徑,而且可能已經存在了,需要到 HashSet 中檢測一下,若存在,后面的部分就不用檢測了,直接 break 掉循環。若所有字符成功遍歷完,說明當前路徑沒有父目錄,可以加到 HashSet 中。最后循環結束后,把 HashSet 中所有的內容放到數組中返回即可,參見代碼如下:


解法一:

class Solution {
public:
    vector<string> removeSubfolders(vector<string>& folder) {
        sort(folder.begin(), folder.end(), [](string& a, string& b) {return a.size() < b.size();});
        unordered_set<string> seen;
        for (string name : folder) {
            int i = 2, n = name.size();
            for (; i < n; ++i) {
                if (name[i] == '/' && seen.count(name.substr(0, i))) {
                    break;
                }
            }
            if (i == n) seen.insert(name);
        }
        return vector<string>(seen.begin(), seen.end());
    }
};

再來看一種解法,這里不是按路徑長度排序,而是按照字母順序排序,這樣相同的父路徑的目錄就會被放到一起,而且長度短的在前面。此時遍歷所有目錄,若結果 res 為空,則可以將當前目錄加入結果中,因為按照排序后的順序,肯定不存在當前目錄的父目錄。若 res 不為空,則將其最后一個目錄取出來,並且加上 '/',看是否是當前路徑的前綴,若不是的話就可以將當前路徑加入結果 res。想一下為何要在后面加上 '/' 呢,因為只有加上了才能保證是父目錄,比如結果 res 的最后一項為 /a,而當前的路徑為 /ab,不加 '/' 的話,則 /a 也是前綴,就出錯了,參見代碼如下:


解法二:

class Solution {
public:
    vector<string> removeSubfolders(vector<string>& folder) {
        vector<string> res;
        sort(folder.begin(), folder.end());
        for (string name : folder) {
            if (res.empty() || name.rfind(res.back() + "/", 0) != 0) {
                res.push_back(name);
            }
        }
        return res;
    }
};

這道題的標簽中還有前綴樹 Trie,表示還可以用這種方法來做。當然首先是要定義前綴樹的結構體,這里由於還存在 '/' 字符,所以很明顯 26 個字符是不夠用的,則 child 數組可以定義為 27 個,將最后一個位置留給 '/' 字符,同時這里還需要一個變量 index,表示當前路徑在給定路徑中的位置,不存在的用 -1 表示,這里相當於一般的前綴樹的中的 isWord 標識。接下來先構建前綴樹,遍歷所有的路徑,並且遍歷路徑上的每一個字符,若是 '/' 字符,則 idx 為 26,否則是其跟小寫字母a之間的距離。若 idx 位置為空,則在該位置上新建 Trie,接下來將指針移動 child[idx] 結點上繼續建樹。當樹建好了之后,就可以開始查找所有的父目錄了。這里用 BFS 來遍歷,將根結點 root 放到 queue 中,然后開始遍歷,取出隊首元素,查看若其 index 大於等於0,則將其加入結果 res 中,由於是 BFS 的順序,則第一個遇到的目錄肯定是父目錄,然后遍歷其 child 結點,若子結點存在,且不是遇到了 '/' 字符或者 index 小於0,則將當前結點放入 queue 繼續遍歷,因為遇到 '/' 字符且 index 大於等於0時,表示是子目錄,這種情況應該跳過,參見代碼如下:


解法三:

class Solution {
public:
    struct Trie {
        Trie *child[27];
        int index = -1;
    };
    
    vector<string> removeSubfolders(vector<string>& folder) {
        Trie* root = new Trie();
        for (int i = 0; i < folder.size(); ++i) {
            Trie *t = root;
            for (char c : folder[i]) {
                int idx = (c == '/') ? 26 : c - 'a';
                if (!t->child[idx]) t->child[idx] = new Trie();
                t = t->child[idx];
            }
            t->index = i;
        }
        return bfs(folder, root);
    }
    
    vector<string> bfs(vector<string>& folder, Trie* root) {
        vector<string> res;
        queue<Trie*> q{{root}};
        while (!q.empty()) {
            Trie *t = q.front(); q.pop();
            if (t->index >= 0) res.push_back(folder[t->index]);
            for (int i = 0; i < 27; ++i) {
                if (t->child[i] && !(i == 26 && t->index >= 0)) {
                    q.push(t->child[i]);
                }
            }
        }
        return res;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1233


參考資料:

https://leetcode.com/problems/remove-sub-folders-from-the-filesystem/

https://leetcode.com/problems/remove-sub-folders-from-the-filesystem/discuss/409028/JavaPython-3-3-methods-from-O(n-*-(logn-%2B-m-2))-to-O(n-*-m)-w-brief-explanation-and-analysis.


LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM