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
10 changes: 8 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-6-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-6-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-7-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-7-blue?style=for-the-badge&logo=python&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems)

</div>

Expand Down Expand Up @@ -199,3 +199,9 @@ This repository covers a comprehensive range of algorithmic patterns and data st
| # | Title | Solution | Time | Space | Difficulty | Tag | Note |
|---|-------|----------|------|-------|------------|-----|------|
| 2313 | [Minimum Flips in Binary Tree to Get Result](https://leetcode.com/problems/minimum-flips-in-binary-tree-to-get-result/) | [Python](./problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py), [C++](./problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc) | _O(n)_ | _O(1)_ | Hard | | _n_ is the number of nodes in the binary tree. |

## Backtracking

| # | Title | Solution | Time | Space | Difficulty | Tag | Note |
|---|-------|----------|------|-------|------------|-----|------|
| 1087 | [Brace Expansion](https://leetcode.com/problems/brace-expansion/) | [Python](./problems/brace_expansion/brace_expansion.py), [C++](./problems/brace_expansion/brace_expansion.cc) | _O(M^K + M log M)_ | _O(M^K)_ | Medium | | M = max choices per brace set, K = number of brace sets. M^K for generating combinations, M log M for sorting. |
85 changes: 85 additions & 0 deletions problems/brace_expansion/brace_expansion.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include "brace_expansion.h"

#include <algorithm>
#include <cstddef>
#include <regex>
#include <string>
#include <utility>
#include <vector>

namespace {

std::vector<std::string> splitByComma(const std::string& str) {
std::vector<std::string> out;
std::size_t start = 0;
while (true) {
const std::size_t pos = str.find(',', start);
if (pos == std::string::npos) {
out.emplace_back(str.substr(start));
break;
}
out.emplace_back(str.substr(start, pos - start));
start = pos + 1;
}
return out;
}

} // namespace

std::vector<std::string> braceExpansion(const std::string& input) {
std::vector<std::string> prefixes;
prefixes.emplace_back(""); // Start with an empty prefix
std::string remaining = input;

// Regex captures (compile once; reuse):
// 1) leading literal before { ([^{}]*)
// 2) choices inside {} ([^}]*)
// 3) literal after } ([^{}]*)
// 4) remaining tail (.*)
static const std::regex REGEX(R"(([^{}]*)\{([^}]*)\}([^{}]*)(.*))");

std::smatch match;
// Iterate group by group until no more braces
while (std::regex_match(remaining, match, REGEX)) {
const std::string& lead = match[1].str();
const std::string& choicesStr = match[2].str();
const std::string& mid = match[3].str();
std::string tail = match[4].str(); // will become new remaining

// Expand choices and sort them
std::vector<std::string> choices = splitByComma(choicesStr);
if (choices.size() > 1) {
std::ranges::sort(choices);
}

std::vector<std::string> newPrefixes;
newPrefixes.reserve(prefixes.size() * std::max(choices.size(), 1UL));
for (const auto& prefix : prefixes) {
for (const auto& choice : choices) {
std::string newPrefix = prefix;
newPrefix += lead;
newPrefix += choice;
newPrefix += mid;
newPrefixes.emplace_back(std::move(newPrefix));
}
}

prefixes.swap(newPrefixes); // reuse memory
remaining.swap(tail); // continue with the tail
}

// Append final remaining tail (no more braces)
std::vector<std::string> results;
results.reserve(prefixes.size());
if (!prefixes.empty()) {
for (const auto& prefix : prefixes) {
std::string result = prefix;
result += remaining;
results.emplace_back(std::move(result));
}
} else {
results.emplace_back(remaining); // input had no braces
}

return results;
}
4 changes: 4 additions & 0 deletions problems/brace_expansion/brace_expansion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include <string>
#include <vector>

std::vector<std::string> braceExpansion(const std::string& input);
53 changes: 53 additions & 0 deletions problems/brace_expansion/brace_expansion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import re

from typing import List


def braceExpansion(s: str) -> List[str]:
results: List[str] = []

def backtrack(prefixes: List[str], remaining: str) -> None:
"""
Recursive helper that expands the string.

prefixes : partial expansions built so far
remaining: the remaining part of the string to process
"""
nonlocal results

# Regex captures:
# 1) leading literal before { ([^{}]*)
# 2) choices inside {} ([^}]*)
# 3) literal after } ([^{}]*)
# 4) remaining tail (.*)
match = re.search(r"([^{}]*)\{([^}]*)\}([^{}]*)(.*)", remaining)

if not match:
# Base case: no more braces left
if prefixes:
# Append remaining to each existing prefix
results.extend(prefix + remaining for prefix in prefixes)
else:
# No prefix: just add the raw remaining string
results.append(remaining)
return

lead, choices_str, mid, tail = match.groups()
choices = sorted(choices_str.split(",")) # expand into sorted list of choices

# If no prefixes yet, start from empty string
base_prefixes = prefixes if prefixes else [""]
new_prefixes: List[str] = []

# Build new partial expansions by combining each prefix with each choice
for prefix in base_prefixes:
for choice in choices:
new_prefixes.append(prefix + lead + choice + mid)

# Recurse on the tail of the string
backtrack(new_prefixes, tail)

# Start recursion with empty prefix and full string
backtrack([], s)

return results
40 changes: 40 additions & 0 deletions problems/brace_expansion/brace_expansion_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "brace_expansion.h"

#include <gtest/gtest.h>
#include <string>
#include <vector>

struct BraceExpansionCase {
const std::string test_name;
const std::string input;
const std::vector<std::string> expected;
};

using BraceExpansionTest = ::testing::TestWithParam<BraceExpansionCase>;

TEST_P(BraceExpansionTest, TestCases) {
const BraceExpansionCase &testCase = GetParam();
const auto result = braceExpansion(testCase.input);
EXPECT_EQ(result, testCase.expected);
}

INSTANTIATE_TEST_SUITE_P(BraceExpansionTestCases, BraceExpansionTest,
::testing::Values(
BraceExpansionCase{
.test_name = "BasicCase",
.input = "{a,b}c{d,e}f",
.expected = {"acdf", "acef", "bcdf", "bcef"},
},
BraceExpansionCase{
.test_name = "NoBraces",
.input = "abcd",
.expected = {"abcd"},
},
BraceExpansionCase{
.test_name = "LeadingLiteralFollowedByBraces",
.input = "k{a,b,c,d,e,f,g}",
.expected = {"ka", "kb", "kc", "kd", "ke", "kf", "kg"},
}),
[](const testing::TestParamInfo<BraceExpansionCase> &info) {
return info.param.test_name;
});
25 changes: 25 additions & 0 deletions problems/brace_expansion/brace_expansion_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Test cases for the brace_expansion function."""

import pytest

from brace_expansion import braceExpansion


@pytest.mark.parametrize(
"s, expected",
[
("{a,b}c{d,e}f", ["acdf", "acef", "bcdf", "bcef"]), # Basic Case
("abcd", ["abcd"]), # No Braces
(
"k{a,b,c,d,e,f,g}",
["ka", "kb", "kc", "kd", "ke", "kf", "kg"],
), # Leading Literal followed by Braces
],
ids=[
"Basic Case",
"No Braces",
"Leading Literal followed by Braces",
],
)
def test_brace_expansion(s, expected):
assert braceExpansion(s) == expected
17 changes: 17 additions & 0 deletions problems/brace_expansion/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
problem:
number: 1087
title: "Brace Expansion"
leetcode_url: "https://leetcode.com/problems/brace-expansion/"
difficulty: "medium"
tags: ["Backtracking"]

solutions:
python: "problems/brace_expansion/brace_expansion.py"
cpp: "problems/brace_expansion/brace_expansion.cc"

complexity:
time: "O(M^K + M log M)"
space: "O(M^K)"

notes: "M = max choices per brace set, K = number of brace sets. M^K for generating combinations, M log M for sorting."
readme_link: ""
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct CalculateAmountPaidInTaxesCase {
using CalculateAmountPaidInTaxesTest = ::testing::TestWithParam<CalculateAmountPaidInTaxesCase>;

TEST_P(CalculateAmountPaidInTaxesTest, TestCases) {
CalculateAmountPaidInTaxesCase testCase = GetParam();
const CalculateAmountPaidInTaxesCase& testCase = GetParam();
const auto result = calculateAmountPaidInTaxes(testCase.brackets, testCase.income);
EXPECT_EQ(result, testCase.expected);
}
Expand All @@ -33,6 +33,6 @@ INSTANTIATE_TEST_SUITE_P(
.brackets = {{2, 50}},
.income = 0,
.expected = 0.00000}),
[](const testing::TestParamInfo<CalculateAmountPaidInTaxesCase> &info) {
[](const testing::TestParamInfo<CalculateAmountPaidInTaxesCase>& info) {
return info.param.test_name;
});