diff --git a/CMakeLists.txt b/CMakeLists.txt index 31c3e54..d4b9090 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,10 +131,12 @@ set(APP_SOURCES "src/core/datatypes/container/unordered/UnorderedMap.cpp" "src/core/expression/Lambda.cpp" "src/core/expression/FunctionPointer.cpp" + ## LC "src/leetcode/arrays/two_sum/TwoSum.cpp" "src/leetcode/arrays/median_two_arrays/MedianTwoSortedArrays.cpp" "src/leetcode/arrays/container_with_most_water/ContainerWithMostWater.cpp" "src/leetcode/arrays/longest_common_prefix/Solution.cpp" + "src/leetcode/arrays/3sum/Solution.cpp" ) # Test files @@ -144,6 +146,7 @@ set(APP_TESTS "tests/median_two_sorted_arrays_ut.cpp" "tests/container_with_most_water_ut.cpp" "tests/longest_common_prefix_ut.cpp" + "tests/three_sum_ut.cpp" ) # ---------------------------------------------------------------------------------------- diff --git a/src/leetcode/arrays/3sum/Solution.cpp b/src/leetcode/arrays/3sum/Solution.cpp index e69de29..78bd324 100644 --- a/src/leetcode/arrays/3sum/Solution.cpp +++ b/src/leetcode/arrays/3sum/Solution.cpp @@ -0,0 +1,65 @@ +#include "Solution.h" +#include +/** + * @brief Complexity + * - Time: O(nlog(n) + n(n-1)) ~ O(n2) + * - Space: O(1) + */ +std::vector> Solution::threeSum(std::vector& nums) { + std::vector> trips{}; + const size_t size = nums.size(); + if (size < 3) { + return trips; + } + + std::sort(nums.begin(), nums.end()); + + // after sorted, if the smallest number is greater than 0, + // no triplet can sum to zero + if (nums[0] > 0) { + return {}; + } + + for (size_t i = 0; i < size - 2; ++i) { + if (i > 0 && nums[i] == nums[i - 1]) { + // we should not use num[i] == num[i+1] expression because we + // may fall thorough out + continue; // skip duplicates for the 1st + } + + int first = nums[i]; + + // use two pointers to find the 2 remain numbers + size_t j = i + 1; + size_t k = size - 1; + while (j < k) { + int second = nums[j]; + int third = nums[k]; + int sum = first + second + third; + + if (sum == 0) { + trips.push_back({first, second, third}); + + ++j; + --k; + + // move next + while (j < k && nums[j] == nums[j - 1]) { + ++j; // skip duplicates for the 2nd + } + + while (j < k && nums[k] == nums[k + 1]) { + --k; // skip duplicates for the 3rd + } + } else if (sum < 0) { + // require a large sum by increasing the 2nd + ++j; + } else { + // require a smaller sum by decreasing the 3rd + --k; + } + } + } + + return trips; +} diff --git a/src/leetcode/arrays/3sum/Solution.h b/src/leetcode/arrays/3sum/Solution.h index e69de29..aa3d3ec 100644 --- a/src/leetcode/arrays/3sum/Solution.h +++ b/src/leetcode/arrays/3sum/Solution.h @@ -0,0 +1,41 @@ +//cppcheck-suppress-file [functionStatic,ctuOneDefinitionRuleViolation] + +// 15 - M +// Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] +// such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0. + +// Notice that the solution set must not contain duplicate triplets. + +// Example 1: +// Input: nums = [-1,0,1,2,-1,-4] +// Output: [[-1,-1,2],[-1,0,1]] +// Explanation: +// nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. +// nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. +// nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. +// The distinct triplets are [-1,0,1] and [-1,-1,2]. +// Notice that the order of the output and the order of the triplets does not matter. + +// Example 2: +// Input: nums = [0,1,1] +// Output: [] +// Explanation: The only possible triplet does not sum up to 0. + +// Example 3: +// Input: nums = [0,0,0] +// Output: [[0,0,0]] +// Explanation: The only possible triplet sums up to 0. + +// Constraints: +// 3 <= nums.length <= 3000 +// -105 <= nums[i] <= 105 + +#pragma once + +#include +#include + +class Solution { + public: + std::vector> threeSum(std::vector& nums); +}; diff --git a/tests/three_sum_ut.cpp b/tests/three_sum_ut.cpp new file mode 100644 index 0000000..11c9ebf --- /dev/null +++ b/tests/three_sum_ut.cpp @@ -0,0 +1,78 @@ +#include +#include + +#include "../src/leetcode/arrays/3sum/Solution.h" + +using ::testing::ElementsAre; +using ::testing::UnorderedElementsAre; + +/** + * Test case definition + */ +struct ThreeSumCase { + std::vector input; + std::vector> expected; +}; + +/** + * Parameterized test fixture + */ +class ThreeSumTest : public ::testing::TestWithParam {}; + +/** + * Main test + * + * Note: + * - Order of triplets does NOT matter + * - Order inside each triplet DOES matter (sorted by implementation) + */ +TEST_P(ThreeSumTest, ReturnsCorrectTriplets) { + const auto& tc = GetParam(); + Solution s{}; + + auto nums = tc.input; + auto result = s.threeSum(nums); + + std::vector<::testing::Matcher>> matchers; + for (const auto& triplet : tc.expected) { + matchers.push_back(ElementsAre(triplet[0], triplet[1], triplet[2])); + } + + EXPECT_THAT(result, UnorderedElementsAreArray(matchers)); +} + +/** + * Test data + */ +INSTANTIATE_TEST_SUITE_P( + BasicAndEdgeCases, ThreeSumTest, + ::testing::Values(ThreeSumCase{{}, {}}, ThreeSumCase{{1}, {}}, + ThreeSumCase{{1, 2}, {}}, ThreeSumCase{{1, 2, 3}, {}}, + ThreeSumCase{{2, 1, 3}, {}}, ThreeSumCase{{0, 1, 1}, {}}, + ThreeSumCase{{-5, -4, -3, -2}, {}}, + ThreeSumCase{{1, 2, 3, 4}, {}}, + + ThreeSumCase{{-1, 0, 1, 2, -1, -4}, + {{-1, -1, 2}, {-1, 0, 1}}}, + + ThreeSumCase{{0, 0, 0}, {{0, 0, 0}}}, + + ThreeSumCase{{0, 0, 0, 0}, {{0, 0, 0}}}, + + ThreeSumCase{{1, 2, 0, 1, 0, 0, 0, 0}, {{0, 0, 0}}}, + + ThreeSumCase{{-2, 0, 0, 2, 2}, {{-2, 0, 2}}})); + +/** + * Extra safety test: + * ensure no duplicate triplets are returned + */ +TEST(ThreeSumExtra, NoDuplicateTriplets) { + std::vector nums{-2, 0, 0, 2, 2}; + Solution s{}; + + auto result = s.threeSum(nums); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result[0], std::vector({-2, 0, 2})); +}