Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

</div>

Expand Down Expand Up @@ -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. |
Comment thread
mathusanm6 marked this conversation as resolved.
Outdated
| 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
Expand Down
17 changes: 17 additions & 0 deletions problems/group_anagrams/config.yml
Original file line number Diff line number Diff line change
@@ -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"
Comment thread
mathusanm6 marked this conversation as resolved.
Outdated

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: ""
28 changes: 28 additions & 0 deletions problems/group_anagrams/group_anagrams.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "group_anagrams.h"

#include <algorithm>
#include <ranges> // NOLINT(misc-include-cleaner) needed for std::ranges::sort
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

using namespace std;

vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> 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<vector<string>> res;
res.reserve(groups.size());
for (auto& [k, v] : groups) {
res.push_back(std::move(v));
}
return res;
}
4 changes: 4 additions & 0 deletions problems/group_anagrams/group_anagrams.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include <string>
#include <vector>

std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs);
13 changes: 13 additions & 0 deletions problems/group_anagrams/group_anagrams.py
Original file line number Diff line number Diff line change
@@ -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())
65 changes: 65 additions & 0 deletions problems/group_anagrams/group_anagrams_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "group_anagrams.h"

#include <gtest/gtest.h>
#include <algorithm>
#include <ranges> // NOLINT(misc-include-cleaner) needed for std::ranges::sort
#include <string>
#include <vector>

struct GroupAnagramsCase {
std::string test_name;
std::vector<std::string> strs;
std::vector<std::vector<std::string>> expected;
};

using GroupAnagramsTest = ::testing::TestWithParam<GroupAnagramsCase>;

namespace {
// Helper function to sort and compare results
bool compareResults(std::vector<std::vector<std::string>> result,
std::vector<std::vector<std::string>> 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<std::string> input = testCase.strs; // Make a copy since function might modify
const std::vector<std::vector<std::string>> 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<GroupAnagramsCase>& info) { return info.param.test_name; });
53 changes: 53 additions & 0 deletions problems/group_anagrams/group_anagrams_test.py
Original file line number Diff line number Diff line change
@@ -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