// Time: O(n * m * l + tlogt + l * t), m is the max number of folders in a path, // , n is the number of paths // , l is the max length of folder name // , t is the size of trie // Space: O(l * t) class Solution { private: template struct PairHash { size_t operator()(const pair& p) const { size_t seed = 0; seed ^= std::hash{}(p.first) + 0x9e3779b9 + (seed<<6) + (seed>>2); seed ^= std::hash{}(p.second) + 0x9e3779b9 + (seed<<6) + (seed>>2); return seed; } }; template struct VectorPairHash { size_t operator()(const std::vector>& v) const { size_t seed = 0; for (const auto& i : v) { seed ^= PairHash{}(i) + 0x9e3779b9 + (seed<<6) + (seed>>2); } return seed; } }; class TrieNode { public: void Insert(const vector& s, unordered_map *folder_ids, unordered_map *id_folders) { auto p = this; for (const auto& c : s) { if (!folder_ids->count(c)) { (*folder_ids)[c] = size(*folder_ids); (*id_folders)[(*folder_ids)[c]] = c; } const auto folder_id = (*folder_ids)[c]; if (!p->leaves.count(folder_id)) { p->leaves[folder_id] = make_unique(); } p = p->leaves[folder_id].get(); } } bool deleted = false; unordered_map> leaves; }; public: vector> deleteDuplicateFolder(vector>& paths) { auto trie = make_unique(); unordered_map folder_ids; unordered_map id_folders; for (const auto& path : paths) { trie->Insert(path, &folder_ids, &id_folders); } unordered_map lookup; unordered_map>, int, VectorPairHash> node_ids; mark(trie.get(), &lookup, &node_ids); vector> result; vector path; sweep(trie.get(), id_folders, &path, &result); return result; } private: int mark(TrieNode *node, unordered_map *lookup, unordered_map>, int, VectorPairHash> *node_ids) { vector> id_pairs; for (const auto& [subfolder_id, child] : node->leaves) { id_pairs.emplace_back(subfolder_id, mark(child.get(), lookup, node_ids)); } sort(begin(id_pairs), end(id_pairs)); if (!node_ids->count(id_pairs)) { (*node_ids)[id_pairs] = size(*node_ids); } int node_id = (*node_ids)[id_pairs]; if (node_id) { if (lookup->count(node_id)) { (*lookup)[node_id]->deleted = true; node->deleted = true; } else { (*lookup)[node_id] = node; } } return node_id; } void sweep(TrieNode *node, const unordered_map& id_folders, vector *path, vector> *result) { if (!empty(*path)) { result->emplace_back(); transform(cbegin(*path), cend(*path), back_inserter(result->back()), [&id_folders](const auto& i) { return id_folders.at(i); }); } for (const auto& [subfolder_id, child] : node->leaves) { if (child->deleted) { continue; } path->emplace_back(subfolder_id); sweep(child.get(), id_folders, path, result); path->pop_back(); } } }; // Time: O(n * m * l + l * tlogt + l * t^2), m is the max number of folders in a path, // , n is the number of paths // , l is the max length of folder name // , t is the size of trie // Space: O(l * t^2) class Solution2 { private: class TrieNode { public: void Insert(const vector& s) { auto p = this; for (const auto& c : s) { if (!p->leaves.count(c)) { p->leaves[c] = make_unique(); } p = p->leaves[c].get(); } } bool deleted = false; map> leaves; }; public: vector> deleteDuplicateFolder(vector>& paths) { auto trie = make_unique(); for (const auto& path : paths) { trie->Insert(path); } unordered_map lookup; mark(trie.get(), &lookup); vector> result; vector path; sweep(trie.get(), &path, &result); return result; } private: string mark(TrieNode *node, unordered_map *lookup) { string serialized_tree = "("; for (const auto& [subfolder, child] : node->leaves) { serialized_tree += subfolder + mark(child.get(), lookup); } serialized_tree += ")"; if (serialized_tree != "()") { if (lookup->count(serialized_tree)) { (*lookup)[serialized_tree]->deleted = true; node->deleted = true; } else { (*lookup)[serialized_tree] = node; } } return serialized_tree; } void sweep(TrieNode *node, vector *path, vector> *result) { if (!empty(*path)) { result->emplace_back(*path); } for (const auto& [subfolder, child] : node->leaves) { if (child->deleted) { continue; } path->emplace_back(subfolder); sweep(child.get(), path, result); path->pop_back(); } } };