From da94cbc7f498b0985e36e2aa2c10d7dbfda7d3a3 Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Fri, 5 Sep 2025 17:38:11 +0200 Subject: [PATCH 1/3] feat: solve 49. Group Anagrams with unit testing --- README.md | 4 +- problems/group_anagrams/config.yml | 17 +++++ problems/group_anagrams/group_anagrams.cc | 28 ++++++++ problems/group_anagrams/group_anagrams.h | 4 ++ problems/group_anagrams/group_anagrams.py | 13 ++++ .../group_anagrams/group_anagrams_test.cc | 65 +++++++++++++++++++ .../group_anagrams/group_anagrams_test.py | 53 +++++++++++++++ 7 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 problems/group_anagrams/config.yml create mode 100644 problems/group_anagrams/group_anagrams.cc create mode 100644 problems/group_anagrams/group_anagrams.h create mode 100644 problems/group_anagrams/group_anagrams.py create mode 100644 problems/group_anagrams/group_anagrams_test.cc create mode 100644 problems/group_anagrams/group_anagrams_test.py diff --git a/README.md b/README.md index 23b3172..9435de2 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ ### 📊 Repository Stats [![Last Commit](https://img.shields.io/github/last-commit/mathusanm6/LeetCode?style=for-the-badge&logo=git&logoColor=white&color=blue)](https://github.com/mathusanm6/LeetCode/commits/main) -[![C++ Solutions](https://img.shields.io/badge/C%2B%2B%20Solutions-4-blue?style=for-the-badge&logo=cplusplus&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) -[![Python Solutions](https://img.shields.io/badge/Python%20Solutions-4-blue?style=for-the-badge&logo=python&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) +[![C++ Solutions](https://img.shields.io/badge/C%2B%2B%20Solutions-5-blue?style=for-the-badge&logo=cplusplus&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) +[![Python Solutions](https://img.shields.io/badge/Python%20Solutions-5-blue?style=for-the-badge&logo=python&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) diff --git a/problems/group_anagrams/config.yml b/problems/group_anagrams/config.yml new file mode 100644 index 0000000..05f37f0 --- /dev/null +++ b/problems/group_anagrams/config.yml @@ -0,0 +1,17 @@ +problem: + number: 49 + title: "Group Anagrams" + leetcode_url: "https://leetcode.com/problems/group-anagrams/" + difficulty: "Medium" + tags: ["Arrays & Hashing"] + +solutions: + python: "problems/group_anagrams/group_anagrams.py" + cpp: "problems/group_anagrams/group_anagrams.cpp" + +complexity: + time: "O(n * k log k)" + space: "O(n)" + +notes: "For C++, the complexity is _O(n * k log k)_, where n is the number of strings and k is the maximum length of a string. But for Python, the complexity is _O(n * k)_ as there is no sorting involved." +readme_link: "" diff --git a/problems/group_anagrams/group_anagrams.cc b/problems/group_anagrams/group_anagrams.cc new file mode 100644 index 0000000..7c5969f --- /dev/null +++ b/problems/group_anagrams/group_anagrams.cc @@ -0,0 +1,28 @@ +#include "group_anagrams.h" + +#include +#include // NOLINT(misc-include-cleaner) needed for std::ranges::sort +#include +#include +#include +#include + +using namespace std; + +vector> groupAnagrams(vector& strs) { + unordered_map> groups; + groups.reserve(strs.size()); + + for (const string& s : strs) { + string key = s; + std::ranges::sort(key); // anagrams share the same sorted key + groups[key].push_back(s); + } + + vector> res; + res.reserve(groups.size()); + for (auto& [k, v] : groups) { + res.push_back(std::move(v)); + } + return res; +} \ No newline at end of file diff --git a/problems/group_anagrams/group_anagrams.h b/problems/group_anagrams/group_anagrams.h new file mode 100644 index 0000000..a2f2f0a --- /dev/null +++ b/problems/group_anagrams/group_anagrams.h @@ -0,0 +1,4 @@ +#include +#include + +std::vector> groupAnagrams(std::vector& strs); \ No newline at end of file diff --git a/problems/group_anagrams/group_anagrams.py b/problems/group_anagrams/group_anagrams.py new file mode 100644 index 0000000..70272f5 --- /dev/null +++ b/problems/group_anagrams/group_anagrams.py @@ -0,0 +1,13 @@ +import collections + +from typing import List + + +def groupAnagrams(strs: List[str]) -> List[List[str]]: + groups = collections.defaultdict(list) + for s in strs: + count = [0] * 26 + for c in s: + count[ord(c) - ord("a")] += 1 + groups[tuple(count)].append(s) + return list(groups.values()) diff --git a/problems/group_anagrams/group_anagrams_test.cc b/problems/group_anagrams/group_anagrams_test.cc new file mode 100644 index 0000000..0f3df23 --- /dev/null +++ b/problems/group_anagrams/group_anagrams_test.cc @@ -0,0 +1,65 @@ +#include "group_anagrams.h" + +#include +#include +#include // NOLINT(misc-include-cleaner) needed for std::ranges::sort +#include +#include + +struct GroupAnagramsCase { + std::string test_name; + std::vector strs; + std::vector> expected; +}; + +using GroupAnagramsTest = ::testing::TestWithParam; + +namespace { +// Helper function to sort and compare results +bool compareResults(std::vector> result, + std::vector> expected) { + // Sort inner vectors and outer vector for comparison + for (auto& group : result) { + std::ranges::sort(group); + } + std::ranges::sort(result); + + for (auto& group : expected) { + std::ranges::sort(group); + } + std::ranges::sort(expected); + + return result == expected; +} +} // namespace + +TEST_P(GroupAnagramsTest, TestCases) { + const GroupAnagramsCase& testCase = GetParam(); + std::vector input = testCase.strs; // Make a copy since function might modify + const std::vector> result = groupAnagrams(input); + EXPECT_TRUE(compareResults(result, testCase.expected)); +} + +INSTANTIATE_TEST_SUITE_P( + GroupAnagramsTestCases, GroupAnagramsTest, + ::testing::Values( + GroupAnagramsCase{.test_name = "BasicCase", + .strs = {"eat", "tea", "tan", "ate", "nat", "bat"}, + .expected = {{"bat"}, {"tan", "nat"}, {"eat", "tea", "ate"}}}, + GroupAnagramsCase{.test_name = "SingleEmptyString", .strs = {""}, .expected = {{""}}}, + GroupAnagramsCase{.test_name = "SingleCharacter", .strs = {"a"}, .expected = {{"a"}}}, + GroupAnagramsCase{.test_name = "MultipleAnagramGroups", + .strs = {"abc", "bca", "cab", "xyz", "zyx", "yxz"}, + .expected = {{"abc", "bca", "cab"}, {"xyz", "zyx", "yxz"}}}, + GroupAnagramsCase{ + .test_name = "LongerStrings", + .strs = {"listen", "silent", "enlist", "inlets", "google", "gogole"}, + .expected = {{"listen", "silent", "enlist", "inlets"}, {"google", "gogole"}}}, + GroupAnagramsCase{.test_name = "AllAnagrams", + .strs = {"aabb", "abab", "bbaa", "baba", "abba"}, + .expected = {{"aabb", "abab", "bbaa", "baba", "abba"}}}, + GroupAnagramsCase{.test_name = "MixedLengths", + .strs = {"rat", "tar", "art", "star", "tars"}, + .expected = {{"rat", "tar", "art"}, {"star", "tars"}}}, + GroupAnagramsCase{.test_name = "EmptyList", .strs = {}, .expected = {}}), + [](const ::testing::TestParamInfo& info) { return info.param.test_name; }); \ No newline at end of file diff --git a/problems/group_anagrams/group_anagrams_test.py b/problems/group_anagrams/group_anagrams_test.py new file mode 100644 index 0000000..f288afd --- /dev/null +++ b/problems/group_anagrams/group_anagrams_test.py @@ -0,0 +1,53 @@ +"""Test cases for the group_anagrams function.""" + +import pytest + +from group_anagrams import groupAnagrams + + +@pytest.mark.parametrize( + "strs, expected", + [ + ( + ["eat", "tea", "tan", "ate", "nat", "bat"], + [["bat"], ["tan", "nat"], ["eat", "tea", "ate"]], + ), # Basic case + ([""], [[""]]), # Single empty string + (["a"], [["a"]]), # Single character + ( + ["abc", "bca", "cab", "xyz", "zyx", "yxz"], + [["abc", "bca", "cab"], ["xyz", "zyx", "yxz"]], + ), # Multiple anagram groups + ( + ["listen", "silent", "enlist", "inlets", "google", "gogole"], + [["listen", "silent", "enlist", "inlets"], ["google", "gogole"]], + ), # Longer strings + ( + ["aabb", "abab", "bbaa", "baba", "abba"], + [["aabb", "abab", "bbaa", "baba", "abba"]], + ), # All are anagrams + ( + ["rat", "tar", "art", "star", "tars"], + [["rat", "tar", "art"], ["star", "tars"]], + ), # Mixed lengths + ([], []), # Empty list + ], + ids=[ + "basic_case", + "single_empty_string", + "single_character", + "multiple_anagram_groups", + "longer_strings", + "all_anagrams", + "mixed_lengths", + "empty_list", + ], +) +def test_group_anagrams(strs, expected): + result = groupAnagrams(strs) + # Sort inner lists and the outer list for comparison + result = [sorted(group) for group in result] + result.sort() + expected = [sorted(group) for group in expected] + expected.sort() + assert result == expected From e91c8362684c9905c3e012c5ff22022905313939 Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Fri, 5 Sep 2025 17:42:19 +0200 Subject: [PATCH 2/3] fix: make readme --- README.md | 1 + problems/group_anagrams/config.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9435de2..92e2f84 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ This repository covers a comprehensive range of algorithmic patterns and data st | # | Title | Solution | Time | Space | Difficulty | Tag | Note | |---|-------|----------|------|-------|------------|-----|------| | 1 | [Two Sum](https://leetcode.com/problems/two-sum/) | [Python](./problems/two_sum/two_sum.py), [C++](./problems/two_sum/two_sum.cc) | _O(n)_ | _O(n)_ | Easy | | | +| 49 | [Group Anagrams](https://leetcode.com/problems/group-anagrams/) | [Python](./problems/group_anagrams/group_anagrams.py), [C++](./problems/group_anagrams/group_anagrams.cpp) | _O(n * k log k)_ | _O(n)_ | Medium | | For C++, the complexity is _O(n * k log k)_, where n is the number of strings and k is the maximum length of a string. But for Python, the complexity is _O(n * k)_ as there is no sorting involved. | | 217 | [Contains Duplicate](https://leetcode.com/problems/contains-duplicate/) | [Python](./problems/contains_duplicate/contains_duplicate.py), [C++](./problems/contains_duplicate/contains_duplicate.cc) | _O(n)_ | _O(n)_ | Easy | | | ## Two Pointers diff --git a/problems/group_anagrams/config.yml b/problems/group_anagrams/config.yml index 05f37f0..3b6b9fe 100644 --- a/problems/group_anagrams/config.yml +++ b/problems/group_anagrams/config.yml @@ -2,7 +2,7 @@ problem: number: 49 title: "Group Anagrams" leetcode_url: "https://leetcode.com/problems/group-anagrams/" - difficulty: "Medium" + difficulty: "medium" tags: ["Arrays & Hashing"] solutions: From 115db59e8ae1ab86d8ddd7c86670d5a8edab62f3 Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Fri, 5 Sep 2025 17:47:03 +0200 Subject: [PATCH 3/3] fix: cpp to cc (bad filepath) --- README.md | 2 +- problems/group_anagrams/config.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92e2f84..f83c998 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ This repository covers a comprehensive range of algorithmic patterns and data st | # | Title | Solution | Time | Space | Difficulty | Tag | Note | |---|-------|----------|------|-------|------------|-----|------| | 1 | [Two Sum](https://leetcode.com/problems/two-sum/) | [Python](./problems/two_sum/two_sum.py), [C++](./problems/two_sum/two_sum.cc) | _O(n)_ | _O(n)_ | Easy | | | -| 49 | [Group Anagrams](https://leetcode.com/problems/group-anagrams/) | [Python](./problems/group_anagrams/group_anagrams.py), [C++](./problems/group_anagrams/group_anagrams.cpp) | _O(n * k log k)_ | _O(n)_ | Medium | | For C++, the complexity is _O(n * k log k)_, where n is the number of strings and k is the maximum length of a string. But for Python, the complexity is _O(n * k)_ as there is no sorting involved. | +| 49 | [Group Anagrams](https://leetcode.com/problems/group-anagrams/) | [Python](./problems/group_anagrams/group_anagrams.py), [C++](./problems/group_anagrams/group_anagrams.cc) | _O(n * k log k)_ | _O(n)_ | Medium | | For C++, the complexity is _O(n * k log k)_, where n is the number of strings and k is the maximum length of a string. But for Python, the complexity is _O(n * k)_ as there is no sorting involved. | | 217 | [Contains Duplicate](https://leetcode.com/problems/contains-duplicate/) | [Python](./problems/contains_duplicate/contains_duplicate.py), [C++](./problems/contains_duplicate/contains_duplicate.cc) | _O(n)_ | _O(n)_ | Easy | | | ## Two Pointers diff --git a/problems/group_anagrams/config.yml b/problems/group_anagrams/config.yml index 3b6b9fe..fbdd33b 100644 --- a/problems/group_anagrams/config.yml +++ b/problems/group_anagrams/config.yml @@ -7,7 +7,7 @@ problem: solutions: python: "problems/group_anagrams/group_anagrams.py" - cpp: "problems/group_anagrams/group_anagrams.cpp" + cpp: "problems/group_anagrams/group_anagrams.cc" complexity: time: "O(n * k log k)"