From c01200bf7f4daaaa8ef81a73057b5bc841b11925 Mon Sep 17 00:00:00 2001 From: Mathusan Selvakumar Date: Fri, 2 Oct 2026 12:17:57 +0200 Subject: [PATCH 1/2] feat: add 88. Merge Sorted Array --- README.md | 5 +- problems/merge_sorted_array/config.yml | 17 +++++++ .../merge_sorted_array/merge_sorted_array.cc | 19 ++++++++ .../merge_sorted_array/merge_sorted_array.h | 5 ++ .../merge_sorted_array/merge_sorted_array.py | 16 +++++++ .../merge_sorted_array_test.cc | 47 +++++++++++++++++++ .../merge_sorted_array_test.py | 24 ++++++++++ 7 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 problems/merge_sorted_array/config.yml create mode 100644 problems/merge_sorted_array/merge_sorted_array.cc create mode 100644 problems/merge_sorted_array/merge_sorted_array.h create mode 100644 problems/merge_sorted_array/merge_sorted_array.py create mode 100644 problems/merge_sorted_array/merge_sorted_array_test.cc create mode 100644 problems/merge_sorted_array/merge_sorted_array_test.py diff --git a/README.md b/README.md index 5dcc293..65f086b 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-8-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-8-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-9-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-9-blue?style=for-the-badge&logo=python&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) @@ -192,6 +192,7 @@ This repository covers a comprehensive range of algorithmic patterns and data st | # | Title | Solution | Time | Space | Difficulty | Tag | Note | |---|-------|----------|------|-------|------------|-----|------| +| 88 | [Merge Sorted Array](https://leetcode.com/problems/merge-sorted-array/) | [Python](./problems/merge_sorted_array/merge_sorted_array.py), [C++](./problems/merge_sorted_array/merge_sorted_array.cc) | _O(m + n)_ | _O(1)_ | Easy | | | | 125 | [Valid Palindrome](https://leetcode.com/problems/valid-palindrome/) | [Python](./problems/valid_palindrome/valid_palindrome.py), [C++](./problems/valid_palindrome/valid_palindrome.cc) | _O(n)_ | _O(1)_ | Easy | | | ## Trees diff --git a/problems/merge_sorted_array/config.yml b/problems/merge_sorted_array/config.yml new file mode 100644 index 0000000..b19bedf --- /dev/null +++ b/problems/merge_sorted_array/config.yml @@ -0,0 +1,17 @@ +problem: + number: 88 + title: "Merge Sorted Array" + leetcode_url: "https://leetcode.com/problems/merge-sorted-array/" + difficulty: "easy" + tags: ["Two Pointers"] + +solutions: + python: "problems/merge_sorted_array/merge_sorted_array.py" + cpp: "problems/merge_sorted_array/merge_sorted_array.cc" + +complexity: + time: "O(m + n)" + space: "O(1)" + +notes: "" +readme_link: "" \ No newline at end of file diff --git a/problems/merge_sorted_array/merge_sorted_array.cc b/problems/merge_sorted_array/merge_sorted_array.cc new file mode 100644 index 0000000..0e81105 --- /dev/null +++ b/problems/merge_sorted_array/merge_sorted_array.cc @@ -0,0 +1,19 @@ +#include "merge_sorted_array.h" +#include + +void merge(std::vector& nums1, int m, std::vector& nums2, int n) { + int i = m - 1; + int j = n - 1; + int k = m + n - 1; + + while (j >= 0) { + if (i >= 0 && nums1[i] >= nums2[j]) { + nums1[k] = nums1[i]; + i -= 1; + } else { + nums1[k] = nums2[j]; + j -= 1; + } + k -= 1; + } +} \ No newline at end of file diff --git a/problems/merge_sorted_array/merge_sorted_array.h b/problems/merge_sorted_array/merge_sorted_array.h new file mode 100644 index 0000000..5a68fc5 --- /dev/null +++ b/problems/merge_sorted_array/merge_sorted_array.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void merge(std::vector& nums1, int m, std::vector& nums2, int n); \ No newline at end of file diff --git a/problems/merge_sorted_array/merge_sorted_array.py b/problems/merge_sorted_array/merge_sorted_array.py new file mode 100644 index 0000000..26e18b3 --- /dev/null +++ b/problems/merge_sorted_array/merge_sorted_array.py @@ -0,0 +1,16 @@ +def merge(nums1: list[int], m: int, nums2: list[int], n: int) -> None: + """ + Do not return anything, modify nums1 in-place instead. + """ + i = m - 1 + j = n - 1 + k = m + n - 1 + + while j >= 0: + if i >= 0 and nums1[i] >= nums2[j]: + nums1[k] = nums1[i] + i -= 1 + else: + nums1[k] = nums2[j] + j -= 1 + k -= 1 diff --git a/problems/merge_sorted_array/merge_sorted_array_test.cc b/problems/merge_sorted_array/merge_sorted_array_test.cc new file mode 100644 index 0000000..ad9e10a --- /dev/null +++ b/problems/merge_sorted_array/merge_sorted_array_test.cc @@ -0,0 +1,47 @@ +#include "merge_sorted_array.h" + +#include +#include +#include + +struct MergeSortedArrayCase { + std::string test_name; + std::vector nums1; + int m; + std::vector nums2; + int n; + std::vector expected; +}; + +using MergeSortedArrayTest = ::testing::TestWithParam; + +TEST_P(MergeSortedArrayTest, TestCases) { + MergeSortedArrayCase testCase = GetParam(); + merge(testCase.nums1, testCase.m, testCase.nums2, testCase.n); + EXPECT_EQ(testCase.nums1, testCase.expected); +} + +INSTANTIATE_TEST_SUITE_P( + MergeSortedArrayTestCases, MergeSortedArrayTest, + ::testing::Values( + MergeSortedArrayCase{.test_name = "basic_example", + .nums1 = {1, 2, 3, 0, 0, 0}, + .m = 3, + .nums2 = {2, 5, 6}, + .n = 3, + .expected = {1, 2, 2, 3, 5, 6}}, + MergeSortedArrayCase{ + .test_name = "both_empty", .nums1 = {}, .m = 0, .nums2 = {}, .n = 0, .expected = {}}, + MergeSortedArrayCase{.test_name = "nums1_empty", + .nums1 = {0}, + .m = 0, + .nums2 = {1}, + .n = 1, + .expected = {1}}, + MergeSortedArrayCase{.test_name = "nums2_empty", + .nums1 = {1}, + .m = 1, + .nums2 = {}, + .n = 0, + .expected = {1}}), + [](const testing::TestParamInfo &info) { return info.param.test_name; }); diff --git a/problems/merge_sorted_array/merge_sorted_array_test.py b/problems/merge_sorted_array/merge_sorted_array_test.py new file mode 100644 index 0000000..a8892ba --- /dev/null +++ b/problems/merge_sorted_array/merge_sorted_array_test.py @@ -0,0 +1,24 @@ +"""Test cases for the merge_sorted_array function.""" + +import pytest +from merge_sorted_array import merge + + +@pytest.mark.parametrize( + "nums1, m, nums2, n, expected", + [ + ([1, 2, 3, 0, 0, 0], 3, [2, 5, 6], 3, [1, 2, 2, 3, 5, 6]), # Basic case + ([], 0, [], 0, []), # Empty arrays + ([0], 0, [1], 1, [1]), # num1 is empty + ([1], 1, [], 0, [1]), # num2 is empty + ], + ids=[ + "basic_case", + "both_empty", + "num1_empty", + "num2_empty", + ], +) +def test_merge(nums1, m, nums2, n, expected): + merge(nums1, m, nums2, n) + assert nums1 == expected From f6f4c3d66c109f61a4a759ca5156f61bd15fd8d4 Mon Sep 17 00:00:00 2001 From: Mathusan Selvakumar Date: Fri, 2 Oct 2026 12:27:43 +0200 Subject: [PATCH 2/2] fix(lint): python 3.14.8 --- .python-version | 1 + common/__init__.py | 2 +- problems/brace_expansion/brace_expansion.py | 10 +++--- .../brace_expansion/brace_expansion_test.py | 1 - .../calculate_amount_paid_in_taxes.py | 5 +-- .../calculate_amount_paid_in_taxes_test.py | 1 - .../contains_duplicate/contains_duplicate.py | 5 +-- .../contains_duplicate_test.py | 1 - .../evaluate_division/evaluate_division.py | 5 ++- .../evaluate_division_test.py | 1 - problems/group_anagrams/group_anagrams.py | 4 +-- .../group_anagrams/group_anagrams_test.py | 1 - ...imum_flips_in_binary_tree_to_get_result.py | 8 ++--- ...flips_in_binary_tree_to_get_result_test.py | 5 +-- problems/two_sum/two_sum.py | 5 +-- problems/two_sum/two_sum_test.py | 1 - .../valid_palindrome/valid_palindrome_test.py | 1 - scripts/generate_readme.py | 34 +++++++++++-------- scripts/update_badges.py | 4 +-- 19 files changed, 40 insertions(+), 55 deletions(-) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..9b8fe2f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14.8 diff --git a/common/__init__.py b/common/__init__.py index a31855b..670b87c 100644 --- a/common/__init__.py +++ b/common/__init__.py @@ -1,3 +1,3 @@ -from .trees.treenode import TreeNode +from common.trees.treenode import TreeNode __all__ = ["TreeNode"] diff --git a/problems/brace_expansion/brace_expansion.py b/problems/brace_expansion/brace_expansion.py index aeb4235..bddb8b7 100644 --- a/problems/brace_expansion/brace_expansion.py +++ b/problems/brace_expansion/brace_expansion.py @@ -1,12 +1,10 @@ import re -from typing import List +def braceExpansion(s: str) -> list[str]: + results: list[str] = [] -def braceExpansion(s: str) -> List[str]: - results: List[str] = [] - - def backtrack(prefixes: List[str], remaining: str) -> None: + def backtrack(prefixes: list[str], remaining: str) -> None: """ Recursive helper that expands the string. @@ -37,7 +35,7 @@ def backtrack(prefixes: List[str], remaining: str) -> None: # If no prefixes yet, start from empty string base_prefixes = prefixes if prefixes else [""] - new_prefixes: List[str] = [] + new_prefixes: list[str] = [] # Build new partial expansions by combining each prefix with each choice for prefix in base_prefixes: diff --git a/problems/brace_expansion/brace_expansion_test.py b/problems/brace_expansion/brace_expansion_test.py index ce5b2b4..57edfd3 100644 --- a/problems/brace_expansion/brace_expansion_test.py +++ b/problems/brace_expansion/brace_expansion_test.py @@ -1,7 +1,6 @@ """Test cases for the brace_expansion function.""" import pytest - from brace_expansion import braceExpansion diff --git a/problems/calculate_amount_paid_in_taxes/calculate_amount_paid_in_taxes.py b/problems/calculate_amount_paid_in_taxes/calculate_amount_paid_in_taxes.py index bc57b3f..b815b2b 100644 --- a/problems/calculate_amount_paid_in_taxes/calculate_amount_paid_in_taxes.py +++ b/problems/calculate_amount_paid_in_taxes/calculate_amount_paid_in_taxes.py @@ -1,7 +1,4 @@ -from typing import List - - -def calculateAmountPaidInTaxes(brackets: List[List[int]], income: int) -> float: +def calculateAmountPaidInTaxes(brackets: list[list[int]], income: int) -> float: total_tax = 0.0 income_remaining = income diff --git a/problems/calculate_amount_paid_in_taxes/calculate_amount_paid_in_taxes_test.py b/problems/calculate_amount_paid_in_taxes/calculate_amount_paid_in_taxes_test.py index 03699e9..31f4cbd 100644 --- a/problems/calculate_amount_paid_in_taxes/calculate_amount_paid_in_taxes_test.py +++ b/problems/calculate_amount_paid_in_taxes/calculate_amount_paid_in_taxes_test.py @@ -1,7 +1,6 @@ """Test cases for the calculate_amount_paid_in_taxes function.""" import pytest - from calculate_amount_paid_in_taxes import calculateAmountPaidInTaxes diff --git a/problems/contains_duplicate/contains_duplicate.py b/problems/contains_duplicate/contains_duplicate.py index d2db556..de9133c 100644 --- a/problems/contains_duplicate/contains_duplicate.py +++ b/problems/contains_duplicate/contains_duplicate.py @@ -1,7 +1,4 @@ -from typing import List - - -def containsDuplicate(nums: List[int]) -> bool: +def containsDuplicate(nums: list[int]) -> bool: seen = set() for num in nums: if num in seen: diff --git a/problems/contains_duplicate/contains_duplicate_test.py b/problems/contains_duplicate/contains_duplicate_test.py index 783c7bb..65ec96c 100644 --- a/problems/contains_duplicate/contains_duplicate_test.py +++ b/problems/contains_duplicate/contains_duplicate_test.py @@ -1,7 +1,6 @@ """Test cases for the contains_duplicate function.""" import pytest - from contains_duplicate import containsDuplicate diff --git a/problems/evaluate_division/evaluate_division.py b/problems/evaluate_division/evaluate_division.py index 0620181..f673c31 100644 --- a/problems/evaluate_division/evaluate_division.py +++ b/problems/evaluate_division/evaluate_division.py @@ -1,10 +1,9 @@ -from typing import List from collections import defaultdict def evaluateDivision( - equations: List[List[str]], values: List[float], queries: List[List[str]] -) -> List[float]: + equations: list[list[str]], values: list[float], queries: list[list[str]] +) -> list[float]: def find(node: str) -> str: if parent[node] != node: original_parent = parent[node] diff --git a/problems/evaluate_division/evaluate_division_test.py b/problems/evaluate_division/evaluate_division_test.py index c50a3cd..e79017e 100644 --- a/problems/evaluate_division/evaluate_division_test.py +++ b/problems/evaluate_division/evaluate_division_test.py @@ -1,7 +1,6 @@ """Test cases for the evaluate_division function.""" import pytest - from evaluate_division import evaluateDivision diff --git a/problems/group_anagrams/group_anagrams.py b/problems/group_anagrams/group_anagrams.py index 70272f5..77e676b 100644 --- a/problems/group_anagrams/group_anagrams.py +++ b/problems/group_anagrams/group_anagrams.py @@ -1,9 +1,7 @@ import collections -from typing import List - -def groupAnagrams(strs: List[str]) -> List[List[str]]: +def groupAnagrams(strs: list[str]) -> list[list[str]]: groups = collections.defaultdict(list) for s in strs: count = [0] * 26 diff --git a/problems/group_anagrams/group_anagrams_test.py b/problems/group_anagrams/group_anagrams_test.py index f288afd..3edd90d 100644 --- a/problems/group_anagrams/group_anagrams_test.py +++ b/problems/group_anagrams/group_anagrams_test.py @@ -1,7 +1,6 @@ """Test cases for the group_anagrams function.""" import pytest - from group_anagrams import groupAnagrams diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py index 9f7ee55..7d2975b 100644 --- a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py +++ b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py @@ -1,10 +1,10 @@ -from common import TreeNode from math import inf -from typing import Optional, Tuple + +from common import TreeNode -def minimumFlips(root: Optional[TreeNode], result: bool) -> int: - def dfs(node: Optional[TreeNode]) -> Tuple[int, int]: +def minimumFlips(root: TreeNode | None, result: bool) -> int: + def dfs(node: TreeNode | None) -> tuple[int, int]: """Returns a tuple (min_flips_to_false, min_flips_to_true)""" if node is None: return inf, inf diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.py b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.py index 11aa385..12ecefe 100644 --- a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.py +++ b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.py @@ -9,8 +9,9 @@ root_dir = Path(__file__).parent.parent.parent sys.path.insert(0, str(root_dir)) -from common import TreeNode # noqa: E402 -from minimum_flips_in_binary_tree_to_get_result import minimumFlips # noqa: E402 +from minimum_flips_in_binary_tree_to_get_result import minimumFlips + +from common import TreeNode @pytest.mark.parametrize( diff --git a/problems/two_sum/two_sum.py b/problems/two_sum/two_sum.py index 46c7d97..08d4888 100644 --- a/problems/two_sum/two_sum.py +++ b/problems/two_sum/two_sum.py @@ -1,7 +1,4 @@ -from typing import List - - -def twoSum(nums: List[int], target: int) -> List[int]: +def twoSum(nums: list[int], target: int) -> list[int]: index_of = {} for idx, num in enumerate(nums): need = target - num diff --git a/problems/two_sum/two_sum_test.py b/problems/two_sum/two_sum_test.py index df4358d..aaf41d8 100644 --- a/problems/two_sum/two_sum_test.py +++ b/problems/two_sum/two_sum_test.py @@ -1,7 +1,6 @@ """Test cases for the two_sum function.""" import pytest - from two_sum import twoSum diff --git a/problems/valid_palindrome/valid_palindrome_test.py b/problems/valid_palindrome/valid_palindrome_test.py index daf1170..afcace3 100644 --- a/problems/valid_palindrome/valid_palindrome_test.py +++ b/problems/valid_palindrome/valid_palindrome_test.py @@ -1,7 +1,6 @@ """Test cases for the isPalindrome function.""" import pytest - from valid_palindrome import isPalindrome diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py index 33c8404..dde78ea 100644 --- a/scripts/generate_readme.py +++ b/scripts/generate_readme.py @@ -1,15 +1,18 @@ -#!/usr/bin/env python3 """ README Generator for LeetCode Solutions Generates the problems table from individual problem configuration files. """ -import os -import yaml import argparse -from pathlib import Path -from typing import Dict, List, Any +import logging +import os from collections import defaultdict +from pathlib import Path +from typing import Any + +import yaml + +logger = logging.getLogger(__name__) class Colors: @@ -49,7 +52,7 @@ def __init__(self, root_dir: str = "."): self.config_dir = self.root_dir / "config" self.problems_dir = self.root_dir / "problems" - def load_config(self, config_file: str) -> Dict[str, Any]: + def load_config(self, config_file: str) -> dict[str, Any]: """Load YAML configuration file.""" config_path = self.config_dir / config_file if not config_path.exists(): @@ -58,7 +61,7 @@ def load_config(self, config_file: str) -> Dict[str, Any]: with open(config_path, "r") as f: return yaml.safe_load(f) - def find_problem_configs(self) -> List[Path]: + def find_problem_configs(self) -> list[Path]: """Find all problem configuration files.""" config_files = [] for root, dirs, files in os.walk(self.problems_dir): @@ -66,12 +69,12 @@ def find_problem_configs(self) -> List[Path]: config_files.append(Path(root) / "config.yml") return config_files - def load_problem_config(self, config_path: Path) -> Dict[str, Any]: + def load_problem_config(self, config_path: Path) -> dict[str, Any]: """Load a single problem configuration.""" with open(config_path, "r") as f: return yaml.safe_load(f) - def validate_problem_config(self, config: Dict[str, Any]) -> bool: + def validate_problem_config(self, config: dict[str, Any]) -> bool: """Validate problem configuration against allowed values.""" difficulties = self.load_config("difficulties.yml").get("difficulties", []) tags = self.load_config("tags.yml").get("tags", []) @@ -104,7 +107,7 @@ def generate_anchor(self, tag: str) -> str: """Generate a markdown anchor from a tag name.""" return tag.lower().replace(" ", "-").replace("&", "") - def format_solutions(self, solutions: Dict[str, str]) -> str: + def format_solutions(self, solutions: dict[str, str]) -> str: """Format solution links for the table.""" formatted_solutions = [] for lang, path in solutions.items(): @@ -138,7 +141,7 @@ def generate_problems_table(self) -> str: problem = config["problem"] for tag in problem.get("tags", ["Uncategorized"]): problems_by_tag[tag].append(config) - except Exception as e: + except (OSError, yaml.YAMLError, KeyError, TypeError, ValueError) as e: print(Colors.red(f"✗ Error loading {config_path}: {e}")) continue @@ -156,7 +159,6 @@ def generate_problems_table(self) -> str: ) # Create section header - tag_anchor = self.generate_anchor(tag) markdown_sections.append(f"## {tag}") markdown_sections.append("") @@ -287,8 +289,10 @@ def update_readme(self, readme_path: str = "README.md"): config = self.load_problem_config(config_path) if self.validate_problem_config(config): valid_configs += 1 - except (yaml.YAMLError, FileNotFoundError, Exception): - pass + except (yaml.YAMLError, FileNotFoundError) as exc: + logger.warning( + "Skipping invalid problem configuration %s: %s", config_path, exc + ) print(Colors.green("✓ README updated successfully!")) print(Colors.green(f" Processed {valid_configs} valid problem configurations")) @@ -308,7 +312,7 @@ def main(): print(Colors.blue("=== LeetCode README Generator ===")) print(Colors.yellow(f"Root directory: {args.root}")) print(Colors.yellow(f"README file: {args.readme}")) - print("") + print() generator = ReadmeGenerator(args.root) generator.update_readme(args.readme) diff --git a/scripts/update_badges.py b/scripts/update_badges.py index 81d32ec..a3475a5 100644 --- a/scripts/update_badges.py +++ b/scripts/update_badges.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Script to automatically update README badges with current problem counts. This script counts the number of solved problems in each language and updates the README.md file. @@ -6,6 +5,7 @@ import os import re +import sys from pathlib import Path @@ -108,4 +108,4 @@ def main(): repo_root = script_dir.parent os.chdir(repo_root) - exit(main()) + sys.exit(main())