Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.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
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.cc"

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