[leetcode 周赛 159] 1233 删除子文件夹

1233 Remove Sub-Folders from the Filesystem 删除子文件夹

问题描述

你是一位系统管理员,手里有一份文件夹列表 folder,你的任务是要删除该列表中的所有 子文件夹,并以 任意顺序 返回剩下的文件夹。

我们这样定义「子文件夹」:

  • 如果文件夹?folder[i]?位于另一个文件夹?folder[j]?下,那么?folder[i]?就是?folder[j]?的子文件夹。

文件夹的「路径」是由一个或多个按以下格式串联形成的字符串:

  • /?后跟一个或者多个小写英文字母。

例如,/leetcode?和?/leetcode/problems?都是有效的路径,而空字符串和?/?不是。

示例 1:

输入: folder = ["/a","/a/b","/c/d","/c/d/e","/c/f"]
输出: ["/a","/c/d","/c/f"]
解释: "/a/b/" 是 "/a" 的子文件夹,而 "/c/d/e" 是 "/c/d" 的子文件夹。

示例 2:

输入: folder = ["/a","/a/b/c","/a/b/d"]
输出: ["/a"]
解释: 文件夹 "/a/b/c" 和 "/a/b/d/" 都会被删除,因为它们都是 "/a" 的子文件夹。

示例 3:

输入: folder = ["/a/b/c","/a/b/d","/a/b/ca"]
输出: ["/a/b/c","/a/b/ca","/a/b/d"]

提示:

  • 1 <= folder.length?<= 4 * 10^4
  • 2 <= folder[i].length <= 100
  • folder[i]?只包含小写字母和 /
  • folder[i]?总是以字符 /?起始
  • 每个文件夹名都是唯一的

思路

  • 读题
    在路径列表中, 选取父路径, 父路径的是子路径的前缀

暴力法(排序+简单剪枝)

路径两两比对, 有可能有三种情况:第一条路径是父路径, 第一条路径是子路径, 两条路径不相关
(A, B) --> (A->B)/(B->A)/(A<-!->B)
标记所有子路径, 在最后避开它们汇集所有父路径

  • 这种作法容易超时, 可以先进行排序, 再筛选两条路径第一个字符匹配的进行比对, 从而缩减比对规模, 减少运算量

寻找规律(排序+集合筛选)

我们可以注意到排序后, 开始的路径都是父路径, 而排在其后的有比较大的可能是其子路径
接着每条路径逐个以/为前缀的路径比对集合中已有路径(这个可以优化)

  1. 没有对比到则其自身是一条父路径, 投入集合中
  2. 比对到则排除

词缀树+DFS

构建一颗以目录名为节点的词缀树, 深度遍历最近的叶子节点

代码实现

暴力法

class Solution {
    public List<String> removeSubfolders(String[] folder) {
        int len = folder.length;
        if (len <= 1) {
            return Collections.singletonList(folder[0]);
        }

        Arrays.sort(folder);
        boolean[] delete = new boolean[len];
        // 每两条路径进行比对 子路径删除(标记删除)
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                // 简单的剪枝 判断第二个字符是否相等作为 比较两路径的前提
                if (!delete[i] && !delete[j] && (folder[i].charAt(1) == folder[j].charAt(1))) {
                    // 路径之间相互比较
                    if (aIsBParent(folder[i], folder[j])) {
                        delete[j] = true;
                    } else if (aIsBParent(folder[j], folder[i])) {
                        delete[i] = true;
                    }
                }
            }
        }

        // 没被标记删除的路径都是父路径
        List<String> ans = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            if (!delete[i]) {
                ans.add(folder[i]);
            }
        }

        return ans;
    }

    private boolean aIsBParent(String a, String b) {
        // 如果a的路径比b的还长 则a必定不是b的父路径
        if (a.length() >= b.length()) {
            return false;
        }

        // 前缀是否相同 [/a/b] -> [/a/b]/c
        return b.charAt(a.length()) == '/' && a.equals(b.substring(0, a.length()));
    }
}

寻找规律

class Solution {
    public List<String> removeSubfolders(String[] folder) {
        int len = folder.length;
        Arrays.sort(folder);
        Set<String> parents = new HashSet<>(len);

        // 指定一条路径 切分其前缀路径比对已知父路径
        for (String f : folder) {
            boolean curIsParent = true;
            int fLen = f.length();
            // 因为是排序的 开始的路径必定是父路径(set存储)
            for (int i = 1; i < fLen; i++) {
                // 切分前缀路径 判断是否是已知父路径
                if (f.charAt(i) == '/' && parents.contains(f.substring(0, i))) {
                    curIsParent = false;
                }
            }

            if (curIsParent) {
                parents.add(f);
            }
        }

        return new ArrayList<>(parents);
    }
}

词缀树

class Solution {
    private class Node {
        String name;
        Map<String, Node> nexts;

        Node() {
            // 结尾标记 普通节点name="" 叶子节点name=path
            name = "";
            nexts = new HashMap<>();
        }

        @Override
        /**
            * debug时 可以显示内容
            */
        public String toString() {
            return "[" + name + "={" + nexts + "}]";
        }
    }

    public List<String> removeSubfolders(String[] folder) {
        // 建立词缀树
        Node root = new Node();
        for (String f : folder) {
            String[] fs = f.split("/");
            // 每次从根节点开始
            Node cur = root;
            for (String s : fs) {
                if (!"".equals(s)) {
                    // 如果没有 则新建节点
                    if (!cur.nexts.containsKey(s)) {
                        cur.nexts.put(s, new Node());
                    }
                    // 切换到下一节点(键值为s)
                    cur = cur.nexts.get(s);
                }
            }
            // 每条路径结尾处 使用name=f做标记
            cur.name = f;
        }

        List<String> ans = new ArrayList<>();
        // 深度优先遍历
        dfs(root, ans);

        return ans;
    }

    private void dfs(Node root, List<String> ans) {
        // 最优先找到的叶子节点 就无需继续往下找了
        if (!"".equals(root.name)) {
            ans.add(root.name);
            return;
        }

        // 遍历当前节点的所有下一节点
        Set<String> set = root.nexts.keySet();
        for (String s : set) {
            // a -> b| -> /a/b
            // a -> b| -> c -> /a/b/c [X]
            // a -> c -> d -> /a/c/d
            dfs(root.nexts.get(s), ans);
        }
    }
}

参考资源

第 159 场周赛 全球排名

原文地址:https://www.cnblogs.com/slowbirdoflsh/p/11779139.html

时间: 2024-10-08 06:14:21

[leetcode 周赛 159] 1233 删除子文件夹的相关文章

小工具:删除某些文件夹 及 其子文件夹

背景:偶最近在一个任务的开发测试的过程中, 需要重复的去运行程序,在测试过程中 每运行一次,会产生大量的 folder 及 file, 尤其是 log file. 有时侯可能不是程序的问题,是其它原因,因为 各个目录下大量的 folder 及 file 会对我造成干扰,本能的想把它们统统干掉,快速的找到出问题的log file 来解决问题.于是便粗制烂造了这个工具,目前它暂时满足偶的需求. 以后有新的需求,可以在此基础上扩展. 下载:http://pan.baidu.com/s/1pJ0mKQ3

重装系统后删除Cygwin文件夹

1.右键点要删除Cygwin 文件夹,依次选属性-安全-高级-所有者-编辑,将所有者改为你的登录帐户,勾选下方“替换子容器和对象的所有者”. 2.在 属性-安全-高级对话框中选 权限选项卡,点更改权限,点添加,输入Everyone,点确定添加Everyone帐户,在弹出的对话框中将完全控制后面的允许勾上,确定. 3.在 属性-安全-高级 高级安全设置对话框中,勾选“使用可从此对象继承的权限替换所有子对象权限”,接下来就可以顺利删除Cygwin文件夹了.

xp下删除windows7,无法删除windows7文件夹,无法删除windows7文件,双系统卸载,取得文件权限

http://blog.csdn.net/lanmanck/article/details/5722050 -------------------------------------------------- 找了比较久,这个强人写的,很佩服,贴出来共享: http://hi.baidu.com/wjg750926/blog/item/4046a84ab171d92b08f7ef27.html 一些网友在Xp 下安装了Windows 7(适用于Vista/WS2008) 双系统,试用一段时间之后

【Java】移动文件夹及其所有子文件与子文件夹

在Java移动文件夹及其所有子文件与子文件夹可以有如下的一段简单的方法来说明: public static void moveFolder(String oldPath, String newPath) { //先复制文件 copyFolder(oldPath, newPath); //则删除源文件,以免复制的时候错乱 deleteDir(new File(oldPath)); } 不应该直接剪切文件,防止在剪切的时候出错,导致这样那样的问题. 在Java复制文件夹及其所有子文件与子文件夹,在<

(转)在lua中递归删除一个文件夹

原文地址:http://www.cocoachina.com/bbs/read.php?tid=212786 纯lua纯 lua 其实是个噱头.这里还是要依赖 lfs(lua file sytem),好在 quick-cocos2d-x 已经包含了这个库.lfs.rmdir 命令 和 os.remove 命令一样,只能删除空文件夹.因此实现类似 rm -rf 的功能, 必须要递归删除文件夹中所有的文件和子文件夹.让我们扩展一下 os 包. ? 1 2 3 4 5 6 7 8 9 10 11 12

php 删除指定文件夹

php 删除指定文件夹 1.前言 目标:php删除一个指定目录 所使用的的php函数:is_dir,opendir,readdir,scandir,rmdir,closedir,等等(注:其他文件操作函数也可以完成,这里只列举了本次使用的函数) 2.相关函数介绍 php文件操作的方法大致相同,已经在上一篇介绍过了,这里就不在重复介绍,这里就介绍一个新函数 rmdir 详情参考:http://www.w3school.com.cn/php/func_filesystem_rmdir.asp 其他函

MFC - 删除指定文件夹

1 // 删除指定的文件夹 2 void DeleteDirectory(CString strDir) 3 { 4 if (strDir.IsEmpty()) 5 { 6 RemoveDirectory(strDir); 7 return; 8 } 9 10 //首先删除文件及子文件夹 11 CFileFind ff; 12 BOOL bFound = ff.FindFile(strDir + _T("\\*"), 0); 13 while (bFound) 14 { 15 bFou

find命令删除当前文件夹下N天前多类文件

find . \( -name "*.gz" -o -name "*.zip" -o -name "*.cfg" \) -maxdepth 1 -mtime +2  -exec rm {} \; 说明: 删除当前文件夹(-maxdepth 1) 2天以前的(-mtime +2) 多类文件 gz zip cfg   删除 -exec rm {} \;

C# 在本地创建文件夹及子文件夹

1 string dict = @"d:\估价报告\"; 2 if (!Directory.Exists(dict)) 3 { 4 Directory.CreateDirectory(dict); //创建文件夹 5 } 6 string subFolder = "subfolder"; 7 string pathString = System.IO.Path.Combine(dict, subFolder); 8 if (!System.IO.File.Exist