LeetCode-in-Java

1948. Delete Duplicate Folders in System

Hard

Due to a bug, there are many duplicate folders in a file system. You are given a 2D array paths, where paths[i] is an array representing an absolute path to the ith folder in the file system.

Two folders (not necessarily on the same level) are identical if they contain the same non-empty set of identical subfolders and underlying subfolder structure. The folders do not need to be at the root level to be identical. If two or more folders are identical, then mark the folders as well as all their subfolders.

Once all the identical folders and their subfolders have been marked, the file system will delete all of them. The file system only runs the deletion once, so any folders that become identical after the initial deletion are not deleted.

Return the 2D array ans containing the paths of the remaining folders after deleting all the marked folders. The paths may be returned in any order.

Example 1:

Input: paths = [[“a”],[“c”],[“d”],[“a”,”b”],[“c”,”b”],[“d”,”a”]]

Output: [[“d”],[“d”,”a”]]

Explanation: The file structure is as shown. Folders “/a” and “/c” (and their subfolders) are marked for deletion because they both contain an empty folder named “b”.

Example 2:

Input: paths = [[“a”],[“c”],[“a”,”b”],[“c”,”b”],[“a”,”b”,”x”],[“a”,”b”,”x”,”y”],[“w”],[“w”,”y”]]

Output: [[“c”],[“c”,”b”],[“a”],[“a”,”b”]]

Explanation: The file structure is as shown. Folders “/a/b/x” and “/w” (and their subfolders) are marked for deletion because they both contain an empty folder named “y”. Note that folders “/a” and “/c” are identical after the deletion, but they are not deleted because they were not marked beforehand.

Example 3:

Input: paths = [[“a”,”b”],[“c”,”d”],[“c”],[“a”]]

Output: [[“c”],[“c”,”d”],[“a”],[“a”,”b”]]

Explanation: All folders are unique in the file system. Note that the returned array can be in a different order as the order does not matter.

Constraints:

Solution

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Solution {
    private Map<String, ArrayList<Folder>> duplicates;
    private List<List<String>> foldersWithRemovedNames;

    public List<List<String>> deleteDuplicateFolder(List<List<String>> paths) {
        duplicates = new HashMap<>();
        Folder rootFolder = new Folder("", null);
        for (List<String> path : paths) {
            Folder folder = rootFolder;
            for (String foldername : path) {
                folder = folder.addSubFolder(foldername);
            }
        }
        rootFolder.calculateHash();
        for (Map.Entry<String, ArrayList<Folder>> entry : duplicates.entrySet()) {
            ArrayList<Folder> foldersWithSameHash = entry.getValue();
            if (foldersWithSameHash != null && foldersWithSameHash.size() > 1) {
                for (Folder folder : foldersWithSameHash) {
                    folder.parent.subFolders.remove(folder.name);
                }
            }
        }
        foldersWithRemovedNames = new ArrayList<>();
        for (Map.Entry<String, Folder> entry : rootFolder.subFolders.entrySet()) {
            Folder folder = entry.getValue();
            List<String> path = new ArrayList<>();
            folder.addPaths(path);
        }
        return foldersWithRemovedNames;
    }

    private class Folder {
        private String name;
        private Map<String, Folder> subFolders;
        private Folder parent;
        private String folderHash;

        private Folder(String folderName, Folder parentFolder) {
            name = folderName;
            subFolders = new HashMap<>();
            folderHash = "";
            parent = parentFolder;
        }

        private Folder addSubFolder(String foldername) {
            return subFolders.computeIfAbsent(foldername, f -> new Folder(f, this));
        }

        private void calculateHash() {
            List<String> subFolderNames = new ArrayList<>(subFolders.keySet());
            Collections.sort(subFolderNames);
            StringBuilder builder = new StringBuilder();
            for (String foldername : subFolderNames) {
                Folder folder = subFolders.get(foldername);
                folder.calculateHash();
                builder.append('#');
                builder.append(foldername);
                if (!folder.folderHash.isEmpty()) {
                    builder.append('(');
                    builder.append(folder.folderHash);
                    builder.append(')');
                }
            }
            folderHash = builder.toString();
            if (!folderHash.isEmpty()) {
                ArrayList<Folder> duplicateFolders =
                        duplicates.computeIfAbsent(folderHash, k -> new ArrayList<>());
                duplicateFolders.add(this);
            }
        }

        private void addPaths(List<String> parentPath) {
            List<String> currentPath = new ArrayList<>(parentPath);
            currentPath.add(name);
            foldersWithRemovedNames.add(currentPath);
            for (Map.Entry<String, Folder> entry : subFolders.entrySet()) {
                Folder folder = entry.getValue();
                folder.addPaths(currentPath);
            }
        }
    }
}