Skip to content

learn: solve 3Sum (#15) via guided TDD#230

Merged
pertrai1 merged 8 commits into
mainfrom
tdd/0015-3sum
Feb 10, 2026
Merged

learn: solve 3Sum (#15) via guided TDD#230
pertrai1 merged 8 commits into
mainfrom
tdd/0015-3sum

Conversation

@pertrai1

@pertrai1 pertrai1 commented Feb 9, 2026

Copy link
Copy Markdown
Owner

Problem

3Sum — Medium

Approach

Sort + Two Pointers pattern. For each fixed element, use two pointers to find pairs that sum to the negative of that element. Skip duplicate fixed elements to avoid duplicate triplets.

Complexity

  • Time: O(n²)
  • Space: O(1) (not counting output)

Pattern

Two Pointers (Fixed Element + Search)

Learning Summary

  • Mode: Coach (learner wrote all implementation code)
  • Cycles: 6 guided cycles
  • Tests: 6 test cases (3 examples + 3 edge cases)
  • Hint Levels Used: Level 1-2 (guiding questions and pattern outline)
  • Key Insight: 3Sum = TwoSum repeated n times - recognize the nested structure
  • Pattern Checkpoints: Initial FAIL (single two-pointer misunderstanding) → PASS after understanding nested structure

🧠 Self-Review Questions

  1. Why do we need to reset the left and right pointers for each iteration of i?
Answer Because for each fixed element at position i, we need to search the entire remaining sorted subarray (from i+1 to end) for pairs that sum to -nums[i]. If we don't reset the pointers, we're only scanning a shrinking portion of the array and will miss valid triplets.
  1. What's the difference between "no duplicate triplets" and "no duplicate values within a triplet"?
Answer "No duplicate triplets" means the result should not contain the same triplet array multiple times (e.g., [[-1, -1, 2], [-1, -1, 2]] is wrong). However, a single triplet CAN have duplicate values (e.g., [-1, -1, 2] is valid). We prevent duplicate triplets by skipping consecutive duplicate values for the fixed element i.

Files

  • README.md — Problem statement
  • 3sum.ts — Solution
  • 3sum.test.ts — Test suite (6 tests)
  • POST_MORTEM.md — Analysis and retrospective

Summary by CodeRabbit

  • New Features

    • Added a complete solution for the 3Sum problem (Medium) using an optimized two-pointer approach.
  • Tests

    • Added a comprehensive unit test suite covering typical cases, edge cases, and boundary conditions.
  • Documentation

    • Added problem README with description, examples, and constraints.
    • Added a detailed post-mortem/analysis and learning guide.
    • Updated the problem index to include the new entry.

@pertrai1 pertrai1 added the code challenge this is a coding challenge label Feb 9, 2026
@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown

Claude finished @pertrai1's task in 1m 41s —— View job


Code Review Complete ✅

  • Read solution implementation
  • Read test suite
  • Read documentation (README, POST_MORTEM)
  • Verify correctness and complexity
  • Provide educational feedback

1. Correctness ✅

The solution is algorithmically correct and handles all edge cases properly. Mental trace with Example 1:

Input: [-1, 0, 1, 2, -1, -4]
Sorted: [-4, -1, -1, 0, 1, 2]

i=0, nums[i]=-4: left=1, right=5
  sum = -4 + -1 + 2 = -3 (< 0, left++)
  sum = -4 + -1 + 2 = -3 (< 0, left++)
  ... no valid triplets

i=1, nums[i]=-1: left=2, right=5
  sum = -1 + -1 + 2 = 0 ✓ → [-1, -1, 2]
  sum = -1 + 0 + 1 = 0 ✓ → [-1, 0, 1]

i=2, nums[i]=-1: SKIP (duplicate of i=1)

Result: [[-1, -1, 2], [-1, 0, 1]] ✅

Edge cases verified:

  • ✅ Empty array
  • ✅ Fewer than 3 elements
  • ✅ All zeros
  • ✅ No valid triplets
  • ✅ All positive numbers

2. Complexity Analysis

Time Complexity: O(n²) ✅ (Correctly stated)

  • Sorting: O(n log n)
  • Outer loop: O(n)
  • Inner two-pointer scan: O(n) per iteration
  • Total: O(n log n + n²) = O(n²)

The comment in 3sum.ts:11 is accurate.

Space Complexity: ⚠️ Inaccuracy Found

Issue in 3sum.ts:12:

// @space = O(n2) for each array inside of the result array

This is incorrect. The space complexity is:

  • O(1) auxiliary space (not counting output)
  • O(k) for output where k = number of triplets found

The claim of O(n²) space for the result is misleading because:

  1. Each triplet is O(1) space (always 3 elements)
  2. Maximum possible triplets is O(n²), but this is the output size, not auxiliary space
  3. Standard convention: complexity analysis excludes output space unless specified

Suggested comment:

// @space O(1) auxiliary space (only using pointers and sum variable)
// Output space is O(k) where k = number of triplets (at most O(n²))

3. Learning Focus - Pattern Recognition

Pattern: Fixed Element + Two Pointers ✅

This is a nested pattern combination, not just "two pointers". The key insight (correctly identified in POST_MORTEM.md:67):

3Sum = TwoSum repeated n times

For each fixed element nums[i], you're solving a TwoSum problem on the remaining sorted subarray: "Find two numbers that sum to -nums[i]".

Why this pattern works:

  1. Sorting enables systematic search and duplicate avoidance
  2. Fixed element reduces 3Sum to a TwoSum subproblem
  3. Two pointers efficiently search the sorted subarray in O(n)
  4. Duplicate skipping is trivial in a sorted array (adjacent duplicates)

Alternative Approaches

  1. Hash Map (O(n²) time, O(n) space):

    // For each pair, check if complement exists in set
    for (let i = 0; i < n; i++) {
      const seen = new Set();
      for (let j = i + 1; j < n; j++) {
        const complement = -(nums[i] + nums[j]);
        if (seen.has(complement)) {
          // Found triplet
        }
        seen.add(nums[j]);
      }
    }

    Trade-off: Same time complexity, but requires O(n) extra space and more complex duplicate handling.

  2. Brute Force (O(n³) time, O(1) space):

    // Three nested loops checking every triplet

    Trade-off: Simpler logic, but too slow for n ≤ 3000.

Your sort + two-pointers approach is optimal for this problem. You can't do better than O(n²) because you must examine at least n² pairs.


4. Code Quality

Strengths:

  • ✅ Clear variable names (left, right, sortedArray)
  • ✅ Good code structure and readability
  • ✅ Helpful comment explaining duplicate skipping (3sum.ts:19)
  • ✅ Comprehensive test coverage (6 tests)

Minor Issues:

Issue 1: Debugging code left in 3sum.ts:44

threeSum([-4, -1, -1, 0, 1, 2]);

This should be removed before merging. It's a function call with no effect (result not used).

Issue 2: Comment accuracy in 3sum.ts:4

// A. Two pointer pattern that will allow to reduce nested loops that cause O(n2) time

This is slightly misleading. The two-pointer pattern doesn't reduce nested loops - it optimizes the inner loop from O(n²) (checking all pairs) to O(n) (converging pointers). The outer loop still causes O(n²) overall complexity.

Better phrasing:

// Two pointer pattern optimizes the inner search from O(n²) to O(n) per fixed element

Issue 3: Comment in 3sum.ts:8

// A. The total must not go below 0 for any of the current values...

This isn't quite the right invariant. The actual invariant is:

  • Sorted array property holds: nums[left] ≤ nums[right]
  • Search space reduces monotonically: Each iteration either increases left or decreases right
  • All possible pairs are considered: When left < right, we examine all valid pairs in the subarray

5. Potential Optimizations

While your solution is optimal in asymptotic complexity, here are micro-optimizations for arrays with many duplicates:

Skip duplicate left/right pointers after finding a match:

if (sum === 0) {
  result.push([sortedArray[i], sortedArray[left], sortedArray[right]]);
  
  // Skip duplicate left values
  while (left < right && sortedArray[left] === sortedArray[left + 1]) {
    left++;
  }
  // Skip duplicate right values
  while (left < right && sortedArray[right] === sortedArray[right - 1]) {
    right--;
  }
  
  left++;
  right--;
}

Early termination when nums[i] > 0:

for (let i = 0; i < sortedArray.length; i++) {
  // If smallest number is positive, no triplet can sum to 0
  if (sortedArray[i] > 0) break;
  
  // ... rest of logic
}

These don't change the O(n²) complexity but reduce constant factors.


6. Related Problems for Practice

These problems use the same "Fixed Element + Two Pointers" pattern:

  1. LeetCode #16 - 3Sum Closest (Medium)

    • Nearly identical structure, but finding the triplet closest to a target instead of exactly zero
    • Same O(n²) approach with two pointers
  2. LeetCode #18 - 4Sum (Medium)

    • Extends the pattern to four numbers (adds another outer loop)
    • O(n³) complexity using nested fixed elements + two pointers
  3. LeetCode #259 - 3Sum Smaller (Medium, Premium)

    • Count triplets where sum < target
    • Same structure but different counting logic
  4. LeetCode #167 - Two Sum II (Sorted Array) (Medium)

    • The foundational two-pointer technique used in your inner loop
    • Good to master before tackling 3Sum

7. Common Mistakes to Avoid

Based on your POST_MORTEM and the pattern:

  1. Not resetting pointers: Always reset left = i + 1 and right = n - 1 for each i
  2. Breaking after first match: Use continue to keep searching, not break
  3. Misunderstanding "no duplicates": Means no duplicate triplet arrays, not no duplicate values within a triplet
  4. Single-pass thinking: This is NOT a single two-pointer problem - it's a nested structure

8. Learning Summary

Key Insight

The "aha moment" is recognizing that 3Sum is NOT a single two-pointer problem. It's a hybrid pattern:

3Sum = TwoSum (two pointers) repeated n times (outer loop)

For each fixed element, you're solving: "Find two numbers in the remaining sorted array that sum to -nums[i]"

What Makes This Solution Work

  1. Sorting transforms the problem into a systematic search
  2. Nested structure (fixed + search) reduces O(n³) brute force to O(n²)
  3. Two pointers leverage sorted property for O(n) search per fixed element
  4. Duplicate skipping is trivial when adjacent values are identical

Pattern Recognition Tip

When you see "find all triplets/quadruplets that satisfy X":

  • Think: Can I reduce this to a simpler subproblem?
  • 3Sum → TwoSum (fix one, search for two)
  • 4Sum → 3Sum (fix one, search for three)

Summary

Overall: Excellent work on this TDD learning exercise! 🎉

Strengths:

  • ✅ Correct algorithm with comprehensive edge case handling
  • ✅ Optimal O(n²) time complexity
  • ✅ Clean, readable code with good variable names
  • ✅ 6 well-designed test cases
  • ✅ Outstanding POST_MORTEM documentation

Areas for Improvement:

  • ⚠️ Fix space complexity comment (currently says O(n²), should be O(1) auxiliary)
  • ⚠️ Remove debugging code on line 44
  • ⚠️ Clarify invariant comments (lines 4 and 8)

Key Takeaway: You've mastered the "Fixed Element + Two Pointers" pattern. This is a foundational technique that extends to 4Sum, 3Sum Closest, and many other problems. The nested structure insight (3Sum = TwoSum × n) is the most important concept to internalize.


@coderabbitai

coderabbitai Bot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds a TypeScript solution for LeetCode "0015 - 3Sum" with a two-pointer implementation, a Vitest test suite, README and POST_MORTEM documentation, and updates docs/PROBLEMS.md to list the problem.

Changes

Cohort / File(s) Summary
Problem Registry
docs/PROBLEMS.md
Inserted "0015 - 3Sum" entries under the Medium problem list and the Two Pointers subsection.
Solution Implementation
leetcode/medium/0015-3sum/3sum.ts
Added export function threeSum(nums: number[]): number[][] using sorting and two pointers; skips duplicates. Includes a direct invocation at file end.
Test Suite
leetcode/medium/0015-3sum/3sum.test.ts
Added Vitest suite "3Sum" with multiple cases (basic duplicates, no triplet, all zeros, empty input, fewer than 3 elements, all positives, extra zeros case).
Documentation
leetcode/medium/0015-3sum/README.md, leetcode/medium/0015-3sum/POST_MORTEM.md
Added problem README with examples/constraints and a detailed POST_MORTEM documenting approaches, edge cases, tests, mistakes, and retrospection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Hop, hop, arrays align,
Pairs and pointers seek the sign,
Three-sum nests in ordered rows,
Tests and notes where knowledge grows,
I nibble bugs and watch it shine. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'learn: solve 3Sum (#15) via guided TDD' directly and clearly reflects the main objective of the pull request—implementing a 3Sum solution using a guided TDD approach—matching the primary change in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tdd/0015-3sum

No actionable comments were generated in the recent review. 🎉


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown

✅ Test Results

Tests have been executed for this pull request.

📊 Coverage Summary

Metric Coverage
Statements 95.08%
Branches 90.3%
Functions 86.95%
Lines 95.12%

Check the workflow logs for detailed test output.

@pertrai1

pertrai1 commented Feb 9, 2026

Copy link
Copy Markdown
Owner Author

🎯 How This Solution Works

Think of 3Sum like organizing a potluck dinner where three people need to bring dishes that perfectly balance out to zero cost. You've got a guest list with everyone's budget (some can contribute, others need help), and you need to find all possible groups of three where the costs cancel out.

The Sorting Magic: First, we line everyone up by their budget from most negative to most positive. This isn't just tidying up—it's the key that unlocks the entire strategy. Once sorted, we know that moving left means "more negative" and moving right means "more positive."

The Fixed Guest Strategy: Here's where it gets clever. For each person (let's call them the "anchor"), we ask: "Who else can I pair with to balance things out?" If our anchor has -4, we need two other people whose budgets sum to +4.

The Two-Pointer Dance: With the remaining guests (everyone after our anchor), we place one pointer at the start (most negative) and one at the end (most positive). It's like adjusting a seesaw:

  • Sum too negative? Nudge the left pointer right to add more positive weight
  • Sum too positive? Nudge the right pointer left to add more negative weight
  • Perfectly balanced? Record this trio and move BOTH pointers to find other combinations

The Duplicate Dodge: Here's a gotcha—some guests have the same budget! If we've already checked an anchor with budget -1, we skip the next -1 to avoid finding the same groups twice. It's like saying "we already checked what this budget can do, no need to repeat ourselves."

The beauty? We check every fixed element once (n iterations) and for each one, we efficiently scan the remaining elements with two pointers (n operations). No triple loops, no chaos—just elegant O(n²) efficiency. It's like we've turned a potentially overwhelming O(n³) guest-matching problem into something much more manageable by being strategic about who we pair together.

@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown

🤖 Gemini Code Review (Model: gemini-2.5-flash)

Here's a review of your 3Sum solution, focusing on educational insights and code quality:


1. Pattern Identification

This solution effectively uses the Two Pointers pattern, specifically a variation known as "Fixed Element + Two Pointers".

  • An outer loop iterates through each element, fixing it as the first number of the triplet.
  • For each fixed element, two pointers (left and right) are used to search the remaining sorted portion of the array for the other two numbers that sum to the target (which is 0 - fixed_element).

This pattern is highly effective for problems like k-Sum when the array can be sorted.

2. Complexity Verification

  • Time Complexity: O(n²)

    • The nums.sort() operation takes O(n log n).
    • The outer for loop runs n times.
    • The inner while loop (the two-pointer scan) runs at most n times for each iteration of the outer loop.
    • Combining these, the total time complexity is O(n log n + n * n), which simplifies to O(n²).
    • Verdict: The stated time complexity of O(n²) in both your 3sum.ts comment and POST_MORTEM.md is accurate.
  • Space Complexity: O(1) auxiliary space, O(k) or O(n²) for output storage

    • The solution uses a constant amount of extra space for variables like i, left, right, sum, and the sortedArray (if sort is in-place or if we consider the input array modified). If the sort creates a new array, it would be O(n). In JavaScript, Array.prototype.sort() typically sorts in-place.
    • The result array, which stores the triplets, is considered output space. In the worst case, the number of triplets k can be up to O(n²). Each triplet stores 3 numbers.
    • Verdict:
      • The POST_MORTEM.md correctly identifies O(1) (not counting output) and O(k) (counting output).
      • The comment in 3sum.ts (@space = O(n2) for each array inside of the result array) is partially inaccurate and misleading. It's more precise to say the auxiliary space is O(1) (or O(n) if sort is not in-place and a copy is made) and the space for the result can be up to O(n²). The O(n2) in your comment seems to refer to the worst-case size of the result array, which is true, but it's important to differentiate between auxiliary space and output space.

3. Key Insight

The "aha!" moment for 3Sum lies in transforming a seemingly cubic problem (three nested loops for brute force) into a quadratic one by leveraging sorting and the Two Pointers pattern.

  1. Sorting as a Prerequisite: Sorting the array nums is crucial. It allows for:

    • Efficient search: The two-pointer approach relies on the array being sorted to move pointers deterministically (if sum < 0, increase left; if sum > 0, decrease right).
    • Easy duplicate handling: In a sorted array, duplicate values are adjacent. This makes it straightforward to skip over them to ensure unique triplets in the output.
  2. Reduction to Two Sum: The core insight is realizing that for each fixed number nums[i], the problem reduces to a "Two Sum" problem on the remaining part of the array. We need to find two numbers (nums[left] and nums[right]) such that nums[left] + nums[right] = -nums[i]. This "Two Sum" subproblem can then be solved efficiently with the two-pointer technique.

4. Edge Case Analysis

Your POST_MORTEM.md and 3sum.test.ts demonstrate excellent coverage of edge cases.

  • Handled Cases (Confirmed by tests and logic):

    • Empty array ([])
    • Array with fewer than 3 elements ([1, 2])
    • Array with all zeros ([0, 0, 0])
    • No valid triplets ([0, 1, 1], [1, 2, 3])
    • Duplicate values within valid triplets (e.g., [-1, -1, 2])
    • Duplicate triplets (skipped by if (i > 0 && sortedArray[i] === sortedArray[i - 1]) continue;)
  • Potential Optimizations / Missed Edge Case Handling (as noted in your POST_MORTEM.md):

    1. Skipping duplicate left and right values: After finding a valid triplet and pushing it to result, you increment left and decrement right. However, to prevent duplicate triplets like [-1, 0, 1] and [-1, 0, 1] if 0 or 1 appears multiple times, you should add logic to skip duplicates for left and right as well:
      // After result.push and left++, right--
      while (left < right && sortedArray[left] === sortedArray[left - 1]) {
          left++;
      }
      while (left < right && sortedArray[right] === sortedArray[right + 1]) {
          right--;
      }
      This is crucial for correctness if the problem statement implies uniqueness even if the fixed i is different but the left/right pair is the same. The current i duplicate check only handles the first element.
    2. Early Termination: If sortedArray[i] becomes positive, you can break the outer loop. Since the array is sorted, all subsequent elements will also be positive, making it impossible to sum to zero with two other positive numbers.
      for (let i = 0; i < sortedArray.length; i++) {
          if (sortedArray[i] > 0) { // Optimization: No way to get 0 if first number is positive
              break;
          }
          // ... rest of the code
      }

5. Learning Points

  • Similar Problems (as identified in your POST_MORTEM.md):

  • Common Mistakes with this Pattern:

    • Forgetting to sort the array: The two-pointer technique relies on sorted data.
    • Incorrect pointer initialization/movement: Not resetting left and right for each iteration of the outer loop, or moving only one pointer after finding a match when both should move.
    • Improper duplicate handling: This is the trickiest part. Forgetting to skip duplicates for the fixed element (i) or for the left/right pointers can lead to duplicate triplets in the result.
    • Not handling edge cases: Especially small arrays (less than 3 elements) or arrays where no solution exists.
  • Variations of this Problem:

    • k-Sum: Find k numbers that sum to a target. This problem (3Sum) is a specific instance of k-Sum. Generally, k-Sum can be solved by nesting the "fixed element" approach k-2 times, and then using two pointers for the final two elements.
    • Target Sum: Instead of summing to 0, sum to an arbitrary target T. The logic remains almost identical.
    • Counting Triplet/Quadruplets: Instead of returning the triplets, just return the count of such triplets.
    • Triplets with conditions: Find triplets where nums[i] < nums[j] < nums[k] or other constraints.

6. Code Quality

Your code is clean, well-structured, and follows good practices:

  • Variable Naming: sortedArray, result, left, right, sum are all descriptive and clear.
  • Code Structure: The sorting, outer loop, and inner two-pointer loop are logically separated and easy to follow.
  • Readability: Good use of comments to explain the pattern and complexity.
  • TypeScript Best Practices: Uses const and let appropriately.
  • Test Cases: 3sum.test.ts provides good coverage.

Minor Improvement:

  • Remove trailing function call: The line threeSum([-4, -1, -1, 0, 1, 2]); at the end of 3sum.ts is likely for local debugging and should be removed from the final solution file to keep it clean and focused solely on the exported function.

7. Alternative Approaches

You correctly identified and analyzed alternative approaches in your POST_MORTEM.md:

  1. Brute Force (O(n³)):

    • Approach: Use three nested loops to check every possible combination of three numbers.
    • Trade-offs: Simplest to conceptualize and implement, but highly inefficient for larger inputs (would TLE on LeetCode for N=3000). Space complexity is O(1) (excluding output).
  2. Hash Map / Hash Set Approach (O(n²)):

    • Approach: Iterate with two nested loops. For each pair (nums[i], nums[j]), calculate the required third number complement = 0 - nums[i] - nums[j]. Then, check if complement exists in a hash set that contains elements processed so far.
    • Trade-offs: Achieves O(n²) time complexity, similar to the two-pointer approach. However, it typically uses O(n) space for the hash set. Duplicate handling can be more complex than with the sorted array approach, often requiring careful management of visited elements or adding unique triplets to a Set<string> (then parsing back) to avoid duplicates. The sorting + two-pointer method is often preferred for its O(1) auxiliary space.

Your chosen Sort + Two Pointers approach is indeed the optimal and most commonly accepted solution for this problem, balancing time efficiency with minimal auxiliary space.

@pertrai1

pertrai1 commented Feb 9, 2026

Copy link
Copy Markdown
Owner Author

💡 Educational Note - The Nested Loop Pattern

Lines 18-39 demonstrate the "Fixed Element + Two Pointers" pattern, which is the key insight for 3Sum:

for (let i = 0; i < sortedArray.length; i++) {
  // ... skip duplicates ...
  let left = i + 1;
  let right = sortedArray.length - 1;
  while (left < right) {
    // two-pointer search
  }
}

Why this works: We're essentially solving TwoSum n times. For each fixed element at position i, we search the remaining sorted subarray for pairs that sum to -nums[i].

Complexity analysis: The outer loop is O(n), and the inner two-pointer scan is O(n), giving us O(n²) total—much better than the O(n³) brute force approach of checking every possible triplet!

@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown

🤖 Gemini Code Review (Model: gemini-2.5-flash)

This is an excellent solution for the 3Sum problem, demonstrating a solid understanding of array manipulation, sorting, and the two-pointer pattern. The POST_MORTEM.md is particularly impressive, providing a thorough self-analysis that aligns well with the learning objectives of this repository.

Here's a detailed review:


1. Pattern Identification

This solution primarily uses the Two Pointers pattern, specifically a variant often called "Fixed Element + Two Pointers" or "N-Sum to (N-1)-Sum reduction".

  • An outer loop iterates through each element, fixing it as one part of the triplet.
  • For each fixed element, two pointers (left and right) are used to search the remaining sorted portion of the array for the other two elements that complete the sum to zero.

This pattern is highly effective for problems that involve finding combinations of numbers that sum to a target in a sorted array, as it efficiently explores possible pairs while handling duplicates.

2. Complexity Verification

  • Time Complexity: O(N^2)

    • The initial sorting of the array takes O(N log N) time.
    • The outer loop iterates N times (where N is the length of nums).
    • The inner while loop (the two-pointer phase) iterates at most N times for each iteration of the outer loop.
    • Combining these, the total time complexity is O(N log N + N * N) = O(N^2).
    • The stated time complexity of O(n2) in the code comment and POST_MORTEM.md is accurate.
  • Space Complexity: O(log N) (or O(N) in worst case) for sorting, O(1) auxiliary space.

    • The nums.sort() method in JavaScript typically uses an Introsort or Timsort-like algorithm, which has a space complexity of O(log N) on average, and O(N) in the worst case for some implementations. If we consider the sort as an in-place modification (which it is), the auxiliary space used by the algorithm itself (for pointers, sum, etc.) is O(1).
    • The POST_MORTEM.md correctly notes O(1) not counting the output array.
    • The comment in 3sum.ts (@space = O(n2) for each array inside of the result array) is a common misunderstanding. Space complexity usually refers to the auxiliary space used by the algorithm, not the space required to store the output. The space for the output itself would be O(k) where k is the number of triplets found. In the worst case, k could be O(N^3) if all combinations formed a triplet (e.g., if all numbers were 0), but for unique triplets, it's typically O(N^2). It's better to clarify that auxiliary space is O(1).

3. Key Insight

The key insight for solving 3Sum efficiently is a two-fold realization:

  1. Reduce to Two Sum: The problem a + b + c = 0 can be rephrased as b + c = -a. This means for each fixed number a (represented by sortedArray[i]), we need to find two other numbers (b and c) in the remaining array that sum up to -a. This transforms the 3Sum problem into N instances of the 2Sum problem.
  2. Leverage Sorting for Efficiency and Duplicates:
    • Efficiency: By sorting the array first, the two-pointer technique can be applied to the b+c subproblem. With pointers left and right moving inwards, we can efficiently find pairs that sum to -a, or adjust the sum by moving left (to increase sum) or right (to decrease sum).
    • Duplicate Handling: Sorting makes it trivial to skip duplicate elements. If sortedArray[i] is the same as sortedArray[i-1], we know any triplets formed with sortedArray[i] would be duplicates of triplets already found with sortedArray[i-1]. Similarly, after finding a valid triplet, we can move left and right past any immediate duplicates to ensure unique triplets in the result.

4. Edge Case Analysis

The provided test suite (3sum.test.ts) is quite comprehensive and covers many important edge cases:

  • Empty array: [] -> [] (Handled correctly)
  • Array with fewer than 3 elements: [1, 2] -> [] (Handled correctly)
  • All zeros: [0, 0, 0] -> [[0, 0, 0]] (Handled correctly)
  • No valid triplets: [0, 1, 1] or [1, 2, 3] -> [] (Handled correctly)
  • Duplicate values within triplets: [-1, -1, 2] (Handled correctly, e.g., for [-1, 0, 1, 2, -1, -4])
  • Duplicate triplets: The if (i > 0 && sortedArray[i] === sortedArray[i - 1]) continue; line correctly prevents adding duplicate triplets formed by the fixed element.

Potential improvements/optimizations for edge cases (identified in POST_MORTEM.md):

  1. Skip duplicate left and right values after finding a match:
    After result.push(...), the left++ and right-- move the pointers. To ensure no duplicate triplets are added due to left or right having the same value as the previous valid triplet, you should add checks:

    if (sum === 0) {
      result.push([sortedArray[i], sortedArray[left], sortedArray[right]]);
      left++;
      right--;
      // Skip duplicates for left pointer
      while (left < right && sortedArray[left] === sortedArray[left - 1]) {
        left++;
      }
      // Skip duplicates for right pointer
      while (left < right && sortedArray[right] === sortedArray[right + 1]) {
        right--;
      }
    }

    This is crucial for cases like [-2, 0, 0, 2, 2] where (-2, 0, 2) is a valid triplet. Without these inner while loops, if left points to the first 0 and right points to the first 2, after finding (-2, 0, 2), left moves to the second 0 and right moves to the second 2. If you don't skip, (-2, 0, 2) might be re-added. However, in the specific example [-2, 0, 0, 2, 2], the original code would find [-2, 0 (first), 2 (first)], then left becomes 1 (second 0), right becomes 3 (second 2). Then sum is still 0, so [-2, 0 (second), 2 (second)] would be added. The while loops prevent this.

  2. Early termination: If sortedArray[i] is positive, then sortedArray[i] + sortedArray[left] + sortedArray[right] (where left > i and right > left) will also be positive, as the array is sorted. Therefore, no triplet can sum to zero.

    for (let i = 0; i < sortedArray.length; i++) {
      if (sortedArray[i] > 0) { // Optimization: If current number is positive, sum cannot be zero
        break;
      }
      // ... rest of the code
    }

    This optimization can save unnecessary computations for arrays containing many positive numbers after some initial negative or zero values.

5. Learning Points

  • Similar problems using this pattern:

  • Common mistakes people make with this pattern:

    • Forgetting to sort: The two-pointer technique relies heavily on the array being sorted.
    • Incorrect duplicate handling: Either missing duplicate checks (leading to duplicate triplets in the output) or over-filtering (missing valid triplets with duplicate numbers).
    • Pointer initialization/movement errors: Forgetting to reset left and right for each i, or not moving both left and right after finding a match.
    • Off-by-one errors: Incorrect loop bounds or pointer increments.
    • Not considering array length constraints: Failing to handle arrays with fewer than 3 elements.
  • Variations of this problem:

    • Find triplets that sum to a specific target (not just zero).
    • Return the count of unique triplets instead of the triplets themselves.
    • Find the triplet with the maximum or minimum sum.
    • Find K numbers that sum to a target (K-Sum problem).

6. Code Quality

The code quality is very good:

  • Variable Naming: sortedArray, left, right, result, sum are all clear and descriptive.
  • Code Structure: The sorting, outer loop, and inner two-pointer logic are well-separated and easy to follow.
  • Readability: The logic is straightforward. The if/else if/else block for adjusting pointers based on sum is standard and highly readable.
  • Comments: The initial comments in 3sum.ts are excellent for self-reflection during the learning process, clearly articulating the thought process. The POST_MORTEM.md serves as a more permanent and detailed record.
  • TypeScript Best Practices: Uses const and let appropriately.

Minor Suggestions for 3sum.ts (incorporating edge case improvements):

  1. Clarify space complexity comment: Update the comment to reflect auxiliary space.
  2. Add early termination for positive nums[i]:
    export function threeSum(nums: number[]): number[][] {
      const sortedArray = nums.sort((a, b) => a - b);
      const result: number[][] = []; // Explicitly type result for clarity
    
      for (let i = 0; i < sortedArray.length; i++) {
        // Optimization: If the current number is positive,
        // and the array is sorted, no subsequent numbers can sum to zero.
        if (sortedArray[i] > 0) {
          break;
        }
    
        // Skip duplicate values for the fixed element to avoid duplicate triplets
        if (i > 0 && sortedArray[i] === sortedArray[i - 1]) {
          continue;
        }
    
        let left = i + 1;
        let right = sortedArray.length - 1;
    
        while (left < right) {
          const sum = sortedArray[i] + sortedArray[left] + sortedArray[right];
          if (sum === 0) {
            result.push([sortedArray[i], sortedArray[left], sortedArray[right]]);
            left++;
            right--;
            // Skip duplicates for left pointer to avoid duplicate triplets
            while (left < right && sortedArray[left] === sortedArray[left - 1]) {
              left++;
            }
            // Skip duplicates for right pointer to avoid duplicate triplets
            while (left < right && sortedArray[right] === sortedArray[right + 1]) {
              right--;
            }
          } else if (sum < 0) {
            left++;
          } else { // sum > 0
            right--;
          }
        }
      }
    
      return result;
    }
    Note: The threeSum([-4, -1, -1, 0, 1, 2]); call at the end of the 3sum.ts file should be removed, as it's typically for local testing during development and not part of the final exportable solution.

7. Alternative Approaches

  1. Brute Force (Three Nested Loops):

    • Iterate with three pointers i, j, k (where i < j < k) and check if nums[i] + nums[j] + nums[k] === 0.
    • Trade-offs: Simple to understand and implement, but very inefficient.
    • Time Complexity: O(N^3)
    • Space Complexity: O(1) (excluding output)
  2. Hash Set Approach (without sorting):

    • Iterate with an outer loop for i. Inside, use a hash set to find pairs (j, k) such that nums[j] + nums[k] = -nums[i].
    • For each nums[j], calculate complement = -nums[i] - nums[j]. If complement is in the hash set, a triplet is found. Add nums[j] to the hash set.
    • Trade-offs: Can avoid initial sorting. Duplicate handling is more complex (e.g., using a set of strings or sorted arrays to store unique triplets).
    • Time Complexity: O(N^2) (average, due to hash set operations)
    • Space Complexity: O(N) (for the hash set)

The chosen "Sort + Two Pointers" approach is generally considered the most optimal balance of time and space complexity for this problem.

@pertrai1

pertrai1 commented Feb 9, 2026

Copy link
Copy Markdown
Owner Author

💡 Educational Note - Duplicate Handling

Lines 20-22 handle a subtle but critical requirement:

if (i > 0 && sortedArray[i] === sortedArray[i - 1]) {
  continue;
}

Common misconception: This does NOT prevent triplets with duplicate values (like [-1, -1, 2]). Those are valid!

What it actually does: Prevents duplicate triplets in the result. If we've already processed all triplets starting with -1, we skip the next -1 to avoid finding the same triplet arrays again.

The sorting advantage: Because the array is sorted, all duplicates are adjacent, making them easy to skip with a simple comparison to the previous element.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e3eb90397

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +29 to +32
if (sum === 0) {
result.push([sortedArray[i], sortedArray[left], sortedArray[right]]);
left++;
right--;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip duplicate pairs after a match to avoid repeat triplets

When a zero-sum triplet is found, the code just increments left and decrements right once, but it does not skip over subsequent equal values. On inputs with repeated values (e.g., [-2,0,0,2,2]), this produces duplicate triplets like [-2,0,2] twice, which violates the problem requirement of unique triplets. Consider advancing left/right past duplicates after pushing a match so each unique pair is emitted once.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@leetcode/medium/0015-3sum/3sum.test.ts`:
- Around line 4-43: Add a unit test that ensures duplicate left/right values
don't produce duplicate triplets: in the test suite for threeSum add a case
calling threeSum with nums = [-2, 0, 0, 2, 2] and assert the result equals [[-2,
0, 2]]; this targets the inner-loop dedup logic in the threeSum implementation
(look for the two-pointer section where left/right are advanced and ensure
duplicate-skipping is exercised).

In `@leetcode/medium/0015-3sum/3sum.ts`:
- Line 44: The file contains a stray direct invocation of the threeSum function
(threeSum([-4, -1, -1, 0, 1, 2])) that runs on import; remove this call so the
function is only executed by tests or callers, or alternatively wrap it in a
top-level guard (e.g., an explicit main/if-run block) so imports don't trigger
execution; locate the literal call to threeSum in the module and delete it (or
move it into a non-import-executed demo section).
- Around line 29-32: The three-sum implementation pushes a valid triplet when
sum === 0 but only does left++ and right--, which allows duplicate triplets for
repeated values; inside the block handling sum === 0 in the function (using
sortedArray, left, right, result and loop variable i) add duplicate-skipping
loops: after incrementing left and decrementing right, advance left while left <
right and sortedArray[left] === sortedArray[left - 1], and similarly decrement
right while left < right and sortedArray[right] === sortedArray[right + 1]; also
ensure the outer loop over i skips duplicate starting values by continuing when
sortedArray[i] === sortedArray[i - 1].

In `@leetcode/medium/0015-3sum/POST_MORTEM.md`:
- Around line 103-104: The comment is saying skipping duplicate left/right
values after finding a valid triplet is required for correctness (not just
optimization); update the three-sum implementation (the loop that uses
left/right two-pointer logic over the sorted nums array, e.g., the code around
variables left, right, and i in the threeSum function) so that after pushing a
found triplet you advance left while nums[left] == nums[left-1] and decrement
right while nums[right] == nums[right+1] to avoid emitting identical triplets,
and ensure you also move left++ and right-- after those skips.
🧹 Nitpick comments (2)
leetcode/medium/0015-3sum/3sum.ts (2)

1-12: Several comment inaccuracies worth correcting.

A few things in the header comments are misleading:

  • Line 4: Says two pointers "reduce nested loops that cause O(n²)" — but the solution itself is still O(n²). The two-pointer technique reduces the brute-force O(n³) to O(n²).
  • Line 8: The stated invariant ("total must not go below 0") doesn't describe the actual algorithm invariant. The real invariant is that left < right with the sorted array guaranteeing the search space narrows toward the target sum.
  • Line 12: Space is stated as O(n²), but the auxiliary space is O(1) excluding output (as correctly noted in POST_MORTEM.md). The output space is O(k) where k = number of triplets.

14-16: Add explicit type annotation to result and avoid mutating the input array.

Per coding guidelines, TypeScript solutions should use explicit type annotations. Also, nums.sort() mutates the caller's array in-place, which can be surprising.

Proposed fix
 export function threeSum(nums: number[]): number[][] {
-  const sortedArray = nums.sort((a, b) => a - b);
-  const result = [];
+  const sortedArray = [...nums].sort((a, b) => a - b);
+  const result: number[][] = [];

As per coding guidelines: "Use explicit type annotations for function parameters and return types in TypeScript solutions."

Comment on lines +4 to +43
describe('3Sum', () => {
it('should find all triplets that sum to zero for basic case', () => {
const nums = [-1, 0, 1, 2, -1, -4];
const result = threeSum(nums);
expect(result).toEqual([
[-1, -1, 2],
[-1, 0, 1]
]);
});

it('should return empty array when no triplets sum to zero', () => {
const nums = [0, 1, 1];
const result = threeSum(nums);
expect(result).toEqual([]);
});

it('should handle array of all zeros', () => {
const nums = [0, 0, 0];
const result = threeSum(nums);
expect(result).toEqual([[0, 0, 0]]);
});

it('should return empty array for empty input', () => {
const nums: number[] = [];
const result = threeSum(nums);
expect(result).toEqual([]);
});

it('should return empty array for array with fewer than 3 elements', () => {
const nums = [1, 2];
const result = threeSum(nums);
expect(result).toEqual([]);
});

it('should return empty array for all positive numbers', () => {
const nums = [1, 2, 3];
const result = threeSum(nums);
expect(result).toEqual([]);
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add a test case with duplicate pairs to catch missing inner-loop dedup.

The current tests don't exercise the scenario where duplicate left/right values produce duplicate triplets. For example, [-2, 0, 0, 2, 2] should return [[-2, 0, 2]] but the current implementation would return it twice. Adding such a test will expose the bug in the solution.

Proposed additional test case
+  it('should not return duplicate triplets when inner values repeat', () => {
+    const nums = [-2, 0, 0, 2, 2];
+    const result = threeSum(nums);
+    expect(result).toEqual([[-2, 0, 2]]);
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe('3Sum', () => {
it('should find all triplets that sum to zero for basic case', () => {
const nums = [-1, 0, 1, 2, -1, -4];
const result = threeSum(nums);
expect(result).toEqual([
[-1, -1, 2],
[-1, 0, 1]
]);
});
it('should return empty array when no triplets sum to zero', () => {
const nums = [0, 1, 1];
const result = threeSum(nums);
expect(result).toEqual([]);
});
it('should handle array of all zeros', () => {
const nums = [0, 0, 0];
const result = threeSum(nums);
expect(result).toEqual([[0, 0, 0]]);
});
it('should return empty array for empty input', () => {
const nums: number[] = [];
const result = threeSum(nums);
expect(result).toEqual([]);
});
it('should return empty array for array with fewer than 3 elements', () => {
const nums = [1, 2];
const result = threeSum(nums);
expect(result).toEqual([]);
});
it('should return empty array for all positive numbers', () => {
const nums = [1, 2, 3];
const result = threeSum(nums);
expect(result).toEqual([]);
});
});
describe('3Sum', () => {
it('should find all triplets that sum to zero for basic case', () => {
const nums = [-1, 0, 1, 2, -1, -4];
const result = threeSum(nums);
expect(result).toEqual([
[-1, -1, 2],
[-1, 0, 1]
]);
});
it('should return empty array when no triplets sum to zero', () => {
const nums = [0, 1, 1];
const result = threeSum(nums);
expect(result).toEqual([]);
});
it('should handle array of all zeros', () => {
const nums = [0, 0, 0];
const result = threeSum(nums);
expect(result).toEqual([[0, 0, 0]]);
});
it('should return empty array for empty input', () => {
const nums: number[] = [];
const result = threeSum(nums);
expect(result).toEqual([]);
});
it('should return empty array for array with fewer than 3 elements', () => {
const nums = [1, 2];
const result = threeSum(nums);
expect(result).toEqual([]);
});
it('should return empty array for all positive numbers', () => {
const nums = [1, 2, 3];
const result = threeSum(nums);
expect(result).toEqual([]);
});
it('should not return duplicate triplets when inner values repeat', () => {
const nums = [-2, 0, 0, 2, 2];
const result = threeSum(nums);
expect(result).toEqual([[-2, 0, 2]]);
});
});
🤖 Prompt for AI Agents
In `@leetcode/medium/0015-3sum/3sum.test.ts` around lines 4 - 43, Add a unit test
that ensures duplicate left/right values don't produce duplicate triplets: in
the test suite for threeSum add a case calling threeSum with nums = [-2, 0, 0,
2, 2] and assert the result equals [[-2, 0, 2]]; this targets the inner-loop
dedup logic in the threeSum implementation (look for the two-pointer section
where left/right are advanced and ensure duplicate-skipping is exercised).

Comment on lines +29 to +32
if (sum === 0) {
result.push([sortedArray[i], sortedArray[left], sortedArray[right]]);
left++;
right--;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Missing duplicate skipping for left/right pointers produces duplicate triplets.

After finding a valid triplet, the code advances left++ and right-- but doesn't skip over consecutive duplicate values. For input [-2, 0, 0, 2, 2], this will push [-2, 0, 2] twice.

Proposed fix
       if (sum === 0) {
         result.push([sortedArray[i], sortedArray[left], sortedArray[right]]);
-        left++;
-        right--;
+        // Skip duplicate left values
+        while (left < right && sortedArray[left] === sortedArray[left + 1]) left++;
+        // Skip duplicate right values
+        while (left < right && sortedArray[right] === sortedArray[right - 1]) right--;
+        left++;
+        right--;
       } else if (sum < 0) {

Based on learnings: "Check for two-pointer technique correctness" and "Verify the solution correctly solves the problem for all test cases, including edge cases."

🤖 Prompt for AI Agents
In `@leetcode/medium/0015-3sum/3sum.ts` around lines 29 - 32, The three-sum
implementation pushes a valid triplet when sum === 0 but only does left++ and
right--, which allows duplicate triplets for repeated values; inside the block
handling sum === 0 in the function (using sortedArray, left, right, result and
loop variable i) add duplicate-skipping loops: after incrementing left and
decrementing right, advance left while left < right and sortedArray[left] ===
sortedArray[left - 1], and similarly decrement right while left < right and
sortedArray[right] === sortedArray[right + 1]; also ensure the outer loop over i
skips duplicate starting values by continuing when sortedArray[i] ===
sortedArray[i - 1].

return result;
}

threeSum([-4, -1, -1, 0, 1, 2]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove the stray direct invocation — likely a debug artifact.

This call executes every time the module is imported (including by tests) and has no side effect besides wasted computation. It could also cause confusion if the module is imported in a production context.

Proposed fix
-
-threeSum([-4, -1, -1, 0, 1, 2]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
threeSum([-4, -1, -1, 0, 1, 2]);
🤖 Prompt for AI Agents
In `@leetcode/medium/0015-3sum/3sum.ts` at line 44, The file contains a stray
direct invocation of the threeSum function (threeSum([-4, -1, -1, 0, 1, 2]))
that runs on import; remove this call so the function is only executed by tests
or callers, or alternatively wrap it in a top-level guard (e.g., an explicit
main/if-run block) so imports don't trigger execution; locate the literal call
to threeSum in the module and delete it (or move it into a non-import-executed
demo section).

Comment on lines +103 to +104
- ⚠️ **Optimization**: Could skip duplicate `left` and `right` values after finding a match for better performance on arrays with many duplicates
- ⚠️ **Early termination**: Could break early if `nums[i] > 0` (all remaining numbers are positive, so no triplet can sum to zero)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Skipping duplicate left/right is a correctness requirement, not just an optimization.

Line 103 frames this as a performance improvement, but it's actually needed for correctness. Without it, inputs like [-2, 0, 0, 2, 2] will produce [-2, 0, 2] twice in the result, violating the "no duplicate triplets" constraint. This should be moved from "Edge cases missed" to a required fix in the implementation.

🤖 Prompt for AI Agents
In `@leetcode/medium/0015-3sum/POST_MORTEM.md` around lines 103 - 104, The comment
is saying skipping duplicate left/right values after finding a valid triplet is
required for correctness (not just optimization); update the three-sum
implementation (the loop that uses left/right two-pointer logic over the sorted
nums array, e.g., the code around variables left, right, and i in the threeSum
function) so that after pushing a found triplet you advance left while
nums[left] == nums[left-1] and decrement right while nums[right] ==
nums[right+1] to avoid emitting identical triplets, and ensure you also move
left++ and right-- after those skips.

@pertrai1

pertrai1 commented Feb 9, 2026

Copy link
Copy Markdown
Owner Author

🔧 Bug Fix: Duplicate Triplet Prevention

Issue Found: The original implementation was returning duplicate triplets for arrays with many duplicate values (e.g., [1, 2, 0, 1, 0, 0, 0, 0] returned [[0, 0, 0], [0, 0, 0]] instead of [[0, 0, 0]]).

Root Cause: We were only skipping duplicate values for the fixed element i in the outer loop, but NOT for the left and right pointers after finding a match.

Fix Applied: Added duplicate skipping for both left and right pointers after finding a valid triplet:

if (sum === 0) {
  result.push([sortedArray[i], sortedArray[left], sortedArray[right]]);
  left++;
  right--;
  // Skip duplicate values for left pointer
  while (left < right && sortedArray[left] === sortedArray[left - 1]) {
    left++;
  }
  // Skip duplicate values for right pointer
  while (left < right && sortedArray[right] === sortedArray[right + 1]) {
    right--;
  }
}

Result: All 7 tests now pass, including the new edge case test for arrays with many duplicate values. ✅

@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown

Claude finished @pertrai1's task in 2m 5s —— View job


Code Review Complete ✅

Reviewing your 3Sum solution as a learning exercise...

Review Checklist:

  • Read solution implementation (3sum.ts)
  • Read test suite (3sum.test.ts)
  • Read documentation (README.md, POST_MORTEM.md)
  • Check recent bug fix commit
  • Verify correctness and complexity
  • Provide educational feedback

1. Correctness ✅

Your solution is algorithmically correct after the duplicate-skipping fix (commit ff357cc). The implementation now properly handles all edge cases. Let me trace through Example 1 to verify:

Input: [-1, 0, 1, 2, -1, -4]
Sorted: [-4, -1, -1, 0, 1, 2]

i=0, nums[i]=-4: left=1, right=5
  sum = -4 + -1 + 2 = -3 (< 0, left++)
  sum = -4 + -1 + 2 = -3 (< 0, left++)
  ...continues but no valid triplets

i=1, nums[i]=-1: left=2, right=5
  sum = -1 + -1 + 2 = 0 ✓ → push [-1, -1, 2]
  left++, right--, then skip duplicates
  sum = -1 + 0 + 1 = 0 ✓ → push [-1, 0, 1]

i=2, nums[i]=-1: SKIP (duplicate of i=1)

Result: [[-1, -1, 2], [-1, 0, 1]] ✅

Edge cases verified:

  • ✅ Empty array → []
  • ✅ Fewer than 3 elements → []
  • ✅ All zeros → [[0, 0, 0]]
  • ✅ No valid triplets → []
  • ✅ All positive numbers → []
  • ✅ Arrays with many duplicates → single [0, 0, 0] (test on line 44-48)

Great catch on the duplicate bug! Your fix in commit ff357cc properly addresses the issue identified by other reviewers by adding inner-loop duplicate skipping (lines 34-40).


2. Complexity Analysis

Time Complexity: O(n²) ✅ Accurate

Your analysis in POST_MORTEM.md is spot-on:

  • Sorting: O(n log n)
  • Outer loop: O(n) iterations
  • Inner two-pointer scan: O(n) per iteration
  • Total: O(n log n + n²) = O(n²)

The comment in 3sum.ts:11 is correct.

Space Complexity: ⚠️ Comment Needs Clarification

Issue in 3sum.ts:12:

// @space = O(n2) for each array inside of the result array

This is misleading. Your POST_MORTEM.md (lines 39-41) has the correct analysis:

  • O(1) auxiliary space (only pointers and sum variable)
  • O(k) output space where k = number of triplets found

The O(n²) in your code comment conflates auxiliary space with output space. Standard complexity convention separates these:

  • Auxiliary space: O(1) (what your algorithm uses besides input/output)
  • Output space: O(k) where k ≤ O(n²) in worst case

Suggested fix for line 12:

// @space O(1) auxiliary space (not counting output array)
// Output space is O(k) where k = number of triplets (at most O(n²))

3. Learning Focus - Pattern Recognition

Pattern: Fixed Element + Two Pointers

You've correctly identified this in your POST_MORTEM (lines 60-63). This is a nested pattern combination, and your key insight (line 67) is excellent:

3Sum = TwoSum repeated n times

This is the "aha moment" that transforms an O(n³) brute force into an O(n²) solution.

Why this pattern works:

  1. Sorting enables systematic search and O(1) duplicate detection (adjacent values)
  2. Fixed element reduces 3Sum to a TwoSum subproblem: "Find two numbers that sum to -nums[i]"
  3. Two pointers exploit sorted property for efficient O(n) search per fixed element
  4. Pointer convergence: If sum < 0, increase left (add more positive); if sum > 0, decrease right (add more negative)

Not just "two pointers": Many students miss that this is a nested structure:

  • Outer loop: O(n) fixed elements
  • Inner loop: O(n) two-pointer search per fixed element
  • Result: O(n²) total

Alternative Approaches

Your POST_MORTEM.md (lines 23-29) correctly analyzes alternatives. Let me add implementation details:

1. Hash Map Approach (O(n²) time, O(n) space):

// For each pair, check if complement exists
for (let i = 0; i < n; i++) {
  const seen = new Set();
  for (let j = i + 1; j < n; j++) {
    const complement = -(nums[i] + nums[j]);
    if (seen.has(complement)) {
      // Found triplet [nums[i], nums[j], complement]
    }
    seen.add(nums[j]);
  }
}

Trade-off: Same time complexity, but uses O(n) extra space and requires complex duplicate handling (often using Set<string> of sorted triplets).

2. Brute Force (O(n³) time, O(1) space):

// Three nested loops checking every combination
for (let i = 0; i < n; i++) {
  for (let j = i + 1; j < n; j++) {
    for (let k = j + 1; k < n; k++) {
      if (nums[i] + nums[j] + nums[k] === 0) {
        // Found triplet
      }
    }
  }
}

Trade-off: Simplest logic, but TLE for n ≤ 3000.

Your sort + two-pointers approach is optimal — you can't do better than O(n²) since you must examine at least n² pairs.


4. Code Quality

Strengths ✨

  • Clear variable names: sortedArray, left, right are descriptive
  • Good test coverage: 7 tests including the duplicate edge case
  • Excellent documentation: POST_MORTEM.md is thorough and educational
  • Proper duplicate handling: Lines 20-22 and 34-40 correctly skip duplicates

Issues Found

1. Debug code left in (line 52):

threeSum([-4, -1, -1, 0, 1, 2]);

This executes on import and should be removed before merging. Fix this →

2. Misleading comment (line 4):

// Two pointer pattern that will allow to reduce nested loops that cause O(n2) time

This is slightly incorrect. The two-pointer pattern doesn't eliminate nested loops — it optimizes the inner loop from O(n²) (checking all pairs) to O(n) (converging pointers). You still have O(n²) overall due to the outer loop.

Better phrasing:

// Two-pointer pattern optimizes the inner search from O(n²) to O(n) per fixed element

3. Unclear invariant comment (line 8):

// The total must not go below 0 for any of the current values...

This doesn't describe the actual invariant. The real invariants are:

  • Sorted property: nums[left] ≤ nums[right]
  • Search space reduction: Each iteration moves left or right, systematically exploring all pairs
  • Convergence: When left < right, all valid pairs in the subarray are considered

5. Potential Optimizations

While your solution is already optimal in asymptotic complexity, here's a micro-optimization for arrays with many positive numbers:

Early Termination

for (let i = 0; i < sortedArray.length; i++) {
  // If the smallest remaining number is positive, no triplet can sum to 0
  if (sortedArray[i] > 0) break;
  
  // ... rest of logic
}

This doesn't change O(n²) complexity but reduces constant factors. Your POST_MORTEM mentions this (line 104) — good awareness!

Note: Your current duplicate-skipping logic (lines 34-40) is already an optimization that prevents redundant work.


6. Related Problems for Practice

Your POST_MORTEM lists excellent related problems (lines 73-78). Let me add context:

Same Pattern Family (Fixed Element + Two Pointers):

  1. LeetCode #16 - 3Sum Closest (Medium)

    • Nearly identical structure, but find triplet closest to target instead of exactly zero
    • Same O(n²) approach with two pointers
  2. LeetCode #18 - 4Sum (Medium)

    • Extends the pattern: two nested fixed elements + two pointers
    • O(n³) complexity (nested pattern continues)
  3. LeetCode #259 - 3Sum Smaller (Medium, Premium)

    • Count triplets where sum < target
    • Same structure but different counting logic

Foundation Problems:

  1. LeetCode #167 - Two Sum II (Sorted Array) (Medium)

    • The core two-pointer technique used in your inner loop
    • Master this before 3Sum
  2. LeetCode #15 - Container With Most Water (Medium)

    • Different two-pointer logic but same convergence principle

7. Common Mistakes to Avoid

Based on your POST_MORTEM "Mistakes & Bugs" section (lines 110-124), you've already identified these — excellent self-reflection!

Let me emphasize the most critical ones:

Mistake 1: Not Resetting Pointers

// ❌ WRONG: Pointers outside loop
let left = 1, right = nums.length - 1;
for (let i = 0; i < nums.length; i++) {
  while (left < right) { /* ... */ }
}

// ✅ CORRECT: Reset for each fixed element
for (let i = 0; i < nums.length; i++) {
  let left = i + 1;
  let right = nums.length - 1;
  while (left < right) { /* ... */ }
}

Mistake 2: Breaking After First Match

// ❌ WRONG: Stops search prematurely
if (sum === 0) {
  result.push([...]);
  break; // Misses other valid triplets!
}

// ✅ CORRECT: Continue searching
if (sum === 0) {
  result.push([...]);
  left++;
  right--;
  // Skip duplicates (lines 34-40)
}

Mistake 3: Misunderstanding "No Duplicates"

Your clarification in the PR description self-review questions is excellent:

"No duplicate triplets" means the result should not contain the same triplet array multiple times (e.g., [[-1, -1, 2], [-1, -1, 2]] is wrong). However, a single triplet CAN have duplicate values (e.g., [-1, -1, 2] is valid).

This is a common confusion point for students.


8. Learning Summary

Key Insight ✨

The transformative insight for 3Sum is recognizing it's NOT a single two-pointer problem but a nested pattern:

3Sum = TwoSum (two pointers) repeated n times (outer loop)
       ↑
   For each fixed element, solve: "Find two numbers that sum to -nums[i]"

What Makes This Solution Work

  1. Sorting transforms chaotic search into systematic exploration
  2. Nested structure (fixed + search) reduces O(n³) brute force to O(n²)
  3. Two pointers leverage sorted property for O(n) search per fixed element
  4. Duplicate skipping is trivial when adjacent values are sorted

Pattern Recognition Tip 🧠

When you see "find all triplets/quadruplets that satisfy X":

  • Think: Can I reduce this to a simpler subproblem?
  • 3Sum → TwoSum (fix one, search for two)
  • 4Sum → 3Sum (fix one, search for three)
  • This is a problem reduction technique

Educational Excellence 🎓

Your POST_MORTEM.md is outstanding — exactly what a learning repository should contain:

  • ✅ Clear problem-solving narrative
  • ✅ Time tracking and self-assessment
  • ✅ Pattern identification and key insights
  • ✅ Honest mistake tracking
  • ✅ Cheat sheet code snippet
  • ✅ Self-rating rubric

This kind of reflection solidifies learning far more than just solving the problem.


Summary

Overall Assessment: Excellent work on this TDD learning exercise! 🎉

Strengths:

  • Correct algorithm with comprehensive edge case handling
  • Optimal O(n²) complexity (can't do better)
  • Clean, readable code with good naming
  • Thorough test suite (7 tests covering all edge cases)
  • Outstanding documentation (POST_MORTEM is exemplary)
  • Self-aware learning (caught and fixed duplicate bug)

Minor Issues to Address:

  1. ⚠️ Remove debug code on line 52 (Fix →)
  2. ⚠️ Clarify space complexity comment (line 12)
  3. ⚠️ Rephrase invariant comments (lines 4 and 8)

Key Takeaway:

You've mastered the "Fixed Element + Two Pointers" pattern — a foundational technique that extends to 4Sum, 3Sum Closest, and many other problems. The nested structure insight (3Sum = TwoSum × n) is the most important concept to internalize.

The fact that you caught the duplicate bug in testing and fixed it (commit ff357cc) shows excellent debugging skills and thorough testing discipline. 👏


@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown

🤖 Gemini Code Review (Model: gemini-2.5-flash)

Here's a detailed review of the 3Sum LeetCode solution, focusing on educational insights and adherence to best practices.


1. Pattern Identification

This solution primarily uses the Two Pointers pattern, specifically a variant often called "Fixed Element + Two Pointers".

  • Fixed Element: An outer loop iterates through the sorted array, fixing one element (nums[i]) at a time.
  • Two Pointers: For each fixed element, two pointers (left and right) are used to search the remaining portion of the sorted array for two other elements that, when combined with the fixed element, sum to the target (which is 0 in this case). The pointers start at the ends of the sub-array and move inwards based on whether the current sum is less than, greater than, or equal to the target.

The crucial preprocessing step is Sorting the input array. Sorting enables both the efficient two-pointer search and simplifies the handling of duplicate triplets.

2. Complexity Verification

  • Time Complexity: The stated time complexity of O(n^2) is accurate.

    • Sorting: nums.sort() takes O(N log N) time.
    • Outer Loop: The for loop iterates N times (where N is the length of nums).
    • Inner Two Pointers: For each iteration of the outer loop, the while loop (with left and right pointers) traverses the remaining portion of the array. In the worst case, this is O(N).
    • Total: Combining these, the total time complexity is O(N log N + N * N), which simplifies to O(N^2).
  • Space Complexity: The stated space complexity of O(n^2) is inaccurate for auxiliary space.

    • Auxiliary Space: This refers to the extra space used by the algorithm itself, excluding the input and output.
      • sortedArray: In JavaScript, Array.prototype.sort() sorts the array in-place. Therefore, const sortedArray = nums.sort(...) does not create a new array, but rather sorts the original nums array and sortedArray becomes a reference to it. This means O(1) auxiliary space for sorting.
      • Pointers (i, left, right), sum variable: These use O(1) space.
      • Thus, the auxiliary space complexity is O(1).
    • Output Space: The result array stores the triplets. In the worst case, the number of unique triplets can be O(N^2) (e.g., an array with many distinct positive and negative numbers). If we count the space taken by the output, it would be O(N^2).
    • Correction: The auxiliary space complexity is O(1). It's important to distinguish between auxiliary space and the space required for the output. The POST_MORTEM.md correctly notes O(1) for auxiliary space.

3. Key Insight

The "aha!" moment for 3Sum lies in transforming a seemingly cubic problem (three nested loops) into a quadratic one by leveraging two key ideas:

  1. Reduce to 2Sum: Instead of searching for three numbers simultaneously, fix one number (nums[i]) with an outer loop. The problem then reduces to finding two numbers (nums[left] and nums[right]) in the remaining array that sum to a specific target = 0 - nums[i]. This effectively turns the 3Sum problem into N instances of the 2Sum problem.
  2. Sorting + Two Pointers for Efficiency and Duplicates:
    • Efficiency: Once the array is sorted, the 2Sum subproblem (finding two numbers that sum to target) can be solved in O(N) time using the two-pointer technique. Since we do this N times, the total becomes O(N^2).
    • Duplicate Handling: Sorting places identical values adjacently. This makes it trivial to skip duplicate numbers for nums[i], nums[left], and nums[right] to ensure that only unique triplets are added to the result.

4. Edge Case Analysis

The provided test suite (3sum.test.ts) is comprehensive and covers many important edge cases:

  • Empty array: []
  • Array with fewer than 3 elements: [1, 2]
  • Array of all zeros: [0, 0, 0]
  • No triplets summing to zero: [0, 1, 1], [1, 2, 3] (all positive)
  • Basic case with multiple unique triplets and duplicates: [-1, 0, 1, 2, -1, -4]
  • Array with many zeros and other numbers: [1, 2, 0, 1, 0, 0, 0, 0] (correctly handles [0,0,0])

Edge cases/optimizations the current solution handles or could be improved upon:

  1. Duplicate i values: Handled correctly by if (i > 0 && sortedArray[i] === sortedArray[i - 1]) continue;.
  2. Duplicate left/right values after finding a match: Handled correctly by the while (left < right && sortedArray[left] === sortedArray[left - 1]) and while (left < right && sortedArray[right] === sortedArray[right + 1]) loops.
  3. Early Termination Optimization: The solution could include an early termination check:
    for (let i = 0; i < sortedArray.length; i++) {
      // Optimization: If the current fixed number is positive,
      // and the array is sorted, all subsequent numbers will also be positive.
      // Thus, no triplet can sum to zero.
      if (sortedArray[i] > 0) {
        break;
      }
      // ... rest of the code
    }
    This optimization can save time on inputs where all possible triplets would sum to a positive number, but it doesn't change the worst-case O(N^2) complexity.

5. Learning Points

  • Similar problems using this pattern:

  • Common mistakes people make with this pattern:

    1. Incorrect Duplicate Handling: Not skipping duplicates for i, left, or right leads to duplicate triplets in the result.
    2. Pointer Initialization/Reset: Forgetting to reset left = i + 1 and right = sortedArray.length - 1 for each iteration of the outer loop.
    3. Moving Pointers After Match: After finding sum === 0, only moving one pointer (e.g., left++) instead of both (left++ and right--) can miss other valid triplets.
    4. Not Sorting First: Attempting to use two pointers on an unsorted array, or trying other approaches that are less efficient or harder to manage duplicates without sorting.
    5. Misunderstanding "No Duplicate Triplets": Confusing it with "no duplicate values within a triplet". The former means the result array should not contain [-1, 0, 1] twice, not that a triplet like [-1, -1, 2] is invalid.
  • Variations of this problem:

    • Finding k numbers that sum to a target (K-Sum).
    • Finding the triplet/quadruplet whose sum is closest to a target.
    • Finding triplets/quadruplets within a specific range.
    • Finding combinations with a target sum, where elements can be reused (often solved with dynamic programming or backtracking).

6. Code Quality

The code quality is excellent:

  • Variable Naming: nums, sortedArray, result, left, right, sum are all descriptive and standard.
  • Code Structure: The sorting, outer loop, and inner two-pointer logic are clearly separated and easy to follow.
  • Readability: The code is very readable due to clear variable names, logical flow, and well-placed comments.
  • Comments: The initial block comment explaining the thought process (pattern, variables, invariant) is a great learning tool. The inline comments for skipping duplicates are also very helpful.
  • TypeScript Best Practices: Uses const and let appropriately. Type annotations are present.

Minor Suggestion:
The line threeSum([-4, -1, -1, 0, 1, 2]); at the end of the 3sum.ts file should be removed. This is typically used for local debugging/testing and should not be part of the final solution submitted to platforms like LeetCode, as it can cause unexpected side effects or execution.

7. Alternative Approaches

  1. Brute Force (Three Nested Loops):

    • Approach: Use three nested for loops (i, j, k) to check every possible combination of three distinct elements.
    • Time Complexity: O(N^3).
    • Space Complexity: O(1) auxiliary space (plus O(N^2) for storing unique triplets in a Set to avoid duplicates).
    • Trade-offs: Very simple to implement but too inefficient for typical constraints (e.g., N=3000). Duplicate handling would require additional logic (e.g., sorting each triplet and storing it in a Set as a string or frozen array).
  2. Hash Map Approach (for 2Sum subproblem):

    • Approach: Iterate through nums with an outer loop (i). For each nums[i], iterate with a second loop (j) from i+1. Calculate complement = 0 - nums[i] - nums[j]. Check if complement exists in a hash set of numbers encountered after j.
    • Time Complexity: O(N^2) (outer loop iterates N times, inner loop with hash set lookup is O(N) on average).
    • Space Complexity: O(N) for the hash set.
    • Trade-offs: Achieves O(N^2) time complexity, similar to the two-pointer approach. However, it uses O(N) auxiliary space for the hash set, making the two-pointer approach more space-efficient (O(1) auxiliary space) when the array is sorted. Duplicate handling can also be slightly more complex with hash sets compared to the sorted array method.

This solution demonstrates a strong understanding of the Two Pointers pattern, efficient duplicate handling, and complexity analysis. The self-reflection in POST_MORTEM.md is particularly valuable for reinforcing learning and identifying areas for improvement.

@pertrai1 pertrai1 requested a review from Copilot February 9, 2026 16:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “3Sum” learning module with a TypeScript implementation, tests, and accompanying documentation/post-mortem.

Changes:

  • Added a 3Sum solution using sort + two pointers with duplicate skipping.
  • Added a Vitest test suite covering examples and edge cases.
  • Added documentation (problem README, post-mortem) and updated the global problems index.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
leetcode/medium/0015-3sum/README.md Adds the problem statement, examples, and constraints for 3Sum.
leetcode/medium/0015-3sum/POST_MORTEM.md Adds an implementation retrospective and pattern notes for learning/TDD.
leetcode/medium/0015-3sum/3sum.ts Implements the 3Sum algorithm (sort + two pointers).
leetcode/medium/0015-3sum/3sum.test.ts Adds unit tests validating expected triplets and common edge cases.
docs/PROBLEMS.md Registers 3Sum in the problems index and the “Two Pointers” section.

Comment on lines +51 to +52

threeSum([-4, -1, -1, 0, 1, 2]);

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This executes threeSum at module import time, which introduces an unwanted side effect (and can break consumers/tests if they import this file). Remove this call, or move it into a dedicated example runner / test.

Suggested change
threeSum([-4, -1, -1, 0, 1, 2]);

Copilot uses AI. Check for mistakes.
* @space = O(n2) for each array inside of the result array
*/
export function threeSum(nums: number[]): number[][] {
const sortedArray = nums.sort((a, b) => a - b);

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorting nums in-place mutates the caller’s input array. If this function is intended to be a pure utility, make a copy before sorting (e.g., clone then sort) to avoid surprising API behavior.

Suggested change
const sortedArray = nums.sort((a, b) => a - b);
const sortedArray = nums.slice().sort((a, b) => a - b);

Copilot uses AI. Check for mistakes.
*/
export function threeSum(nums: number[]): number[][] {
const sortedArray = nums.sort((a, b) => a - b);
const result = [];

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In TypeScript, const result = [] will default to any[] in many configurations, losing type safety. Explicitly type this as number[][] (or initialize in a way that preserves the intended type) to keep strong typing through push and return.

Suggested change
const result = [];
const result: number[][] = [];

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +12
* A. Two pointer pattern that will allow to reduce nested loops that cause O(n2) time
* What variables will you need to track as you iterate?
* A. A left pointer, a right pointer, current item in nums, and a results array
* What property must remain true throughout your algorithm?
* A. The total must not go below 0 for any of the current values of the variables left, right, current
*
* Time and Space complexity
* @time - O(n2) because of the inner while loop that runs for each element in the sortedArray
* @space = O(n2) for each array inside of the result array

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment has incorrect/misleading statements: (1) the invariant about the total not going below 0 is not true for 3Sum (negative sums are part of the search); (2) space complexity is not O(n²) as written—auxiliary space is O(1) excluding output; (3) prefer standard notation O(n^2) for readability.

Suggested change
* A. Two pointer pattern that will allow to reduce nested loops that cause O(n2) time
* What variables will you need to track as you iterate?
* A. A left pointer, a right pointer, current item in nums, and a results array
* What property must remain true throughout your algorithm?
* A. The total must not go below 0 for any of the current values of the variables left, right, current
*
* Time and Space complexity
* @time - O(n2) because of the inner while loop that runs for each element in the sortedArray
* @space = O(n2) for each array inside of the result array
* A. Two-pointer pattern on a sorted array that reduces the brute-force O(n^3) search to O(n^2) time.
* What variables will you need to track as you iterate?
* A. A left pointer, a right pointer, current item in nums, and a results array.
* What property must remain true throughout your algorithm?
* A. The array remains sorted and the left and right pointers move inward (left++ when sum < 0, right-- when sum > 0), while skipping duplicates to avoid duplicate triplets.
*
* Time and Space complexity
* @time - O(n^2) because of the inner while loop that runs for each element in the sortedArray.
* @space - O(1) auxiliary space (excluding the O(k) space needed to store the resulting triplets).

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +12
* A. Two pointer pattern that will allow to reduce nested loops that cause O(n2) time
* What variables will you need to track as you iterate?
* A. A left pointer, a right pointer, current item in nums, and a results array
* What property must remain true throughout your algorithm?
* A. The total must not go below 0 for any of the current values of the variables left, right, current
*
* Time and Space complexity
* @time - O(n2) because of the inner while loop that runs for each element in the sortedArray
* @space = O(n2) for each array inside of the result array

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment has incorrect/misleading statements: (1) the invariant about the total not going below 0 is not true for 3Sum (negative sums are part of the search); (2) space complexity is not O(n²) as written—auxiliary space is O(1) excluding output; (3) prefer standard notation O(n^2) for readability.

Suggested change
* A. Two pointer pattern that will allow to reduce nested loops that cause O(n2) time
* What variables will you need to track as you iterate?
* A. A left pointer, a right pointer, current item in nums, and a results array
* What property must remain true throughout your algorithm?
* A. The total must not go below 0 for any of the current values of the variables left, right, current
*
* Time and Space complexity
* @time - O(n2) because of the inner while loop that runs for each element in the sortedArray
* @space = O(n2) for each array inside of the result array
* A. Two pointer pattern that will allow to reduce nested loops that cause O(n^2) time
* What variables will you need to track as you iterate?
* A. A left pointer, a right pointer, current item in nums, and a results array
* What property must remain true throughout your algorithm?
* A. The array remains sorted, the two pointers move inward while left < right, and no duplicate triplets are added to the result.
*
* Time and Space complexity
* @time - O(n^2) because of the inner while loop that runs for each element in the sortedArray
* @space - O(1) auxiliary space (excluding the output; storing the result triplets takes O(k) space for k triplets)

Copilot uses AI. Check for mistakes.
const sortedArray = nums.sort((a, b) => a - b);
const result = [];

for (let i = 0; i < sortedArray.length; i++) {

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outer loop can stop at sortedArray.length - 2 (since you need at least 3 elements), and you can break early once sortedArray[i] > 0 because all remaining values are ≥ sortedArray[i] in a sorted array, so no triplet can sum to zero. This reduces unnecessary iterations on larger inputs.

Suggested change
for (let i = 0; i < sortedArray.length; i++) {
for (let i = 0; i < sortedArray.length - 2; i++) {
// Since the array is sorted, once the fixed element is > 0, no triplet can sum to 0
if (sortedArray[i] > 0) {
break;
}

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +25
let left = i + 1;
let right = sortedArray.length - 1;

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outer loop can stop at sortedArray.length - 2 (since you need at least 3 elements), and you can break early once sortedArray[i] > 0 because all remaining values are ≥ sortedArray[i] in a sorted array, so no triplet can sum to zero. This reduces unnecessary iterations on larger inputs.

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +11
expect(result).toEqual([
[-1, -1, 2],
[-1, 0, 1]
]);

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3Sum output order is not guaranteed by the problem; asserting a specific array ordering can make tests brittle even if the solution is correct. Consider normalizing (sort triplets and sort the list of triplets) before comparing, or compare as an unordered set-like structure.

Copilot uses AI. Check for mistakes.
expect(result).toEqual([]);
});

it('should return multiple arrays with same value', () => {

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test name is unclear/misleading: the expectation asserts a single triplet [[0, 0, 0]]. Consider renaming to reflect the intent (e.g., handling many duplicate zeros / ensuring de-duplication of triplets).

Suggested change
it('should return multiple arrays with same value', () => {
it('should de-duplicate triplets when multiple zeros are present', () => {

Copilot uses AI. Check for mistakes.
@pertrai1 pertrai1 merged commit 166ee2d into main Feb 10, 2026
8 of 10 checks passed
@pertrai1 pertrai1 deleted the tdd/0015-3sum branch February 10, 2026 00:22
@github-actions

Copy link
Copy Markdown

📅 Spaced Repetition Reviews Scheduled!

Great job solving #15 - 3sum! 🎉

To help you retain this knowledge long-term, I've scheduled 5 review sessions using spaced repetition:

Review Interval Schedule Logic
1st Review 1 day after solving Scheduled now
2nd Review 3 days after 1st review Auto-scheduled when 1st completes
3rd Review 7 days after 2nd review Auto-scheduled when 2nd completes
4th Review 14 days after 3rd review Auto-scheduled when 3rd completes
5th Review 30 days after 4th review Auto-scheduled when 4th completes

What to expect:

  • Your 1st review is scheduled for tomorrow
  • Each subsequent review is scheduled automatically when you complete the previous one
  • This ensures proper spacing even if you complete a review a few days late
  • GitHub issues will be created automatically for each review
  • Each issue will link back to your solution

🧠 Why Spaced Repetition?

Research shows that reviewing material at increasing intervals dramatically improves retention. This adaptive scheduling ensures optimal spacing based on when you actually complete each review!

Check docs/reviews/review-schedule.json to see your review schedule.

github-actions Bot pushed a commit that referenced this pull request Feb 10, 2026
  Problem: #15 - 3sum
  PR: #230
  First review scheduled (subsequent reviews auto-scheduled on completion)

  [skip ci]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code challenge this is a coding challenge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants