From 60687302e68f05c08bf3a565d13de8fbab875b79 Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Mon, 9 Feb 2026 09:50:49 -0500 Subject: [PATCH 1/8] learn: cycle 1 - implement basic 3Sum with two pointers --- leetcode/medium/0015-3sum/3sum.test.ts | 13 +++++++ leetcode/medium/0015-3sum/3sum.ts | 44 ++++++++++++++++++++++++ leetcode/medium/0015-3sum/README.md | 47 ++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 leetcode/medium/0015-3sum/3sum.test.ts create mode 100644 leetcode/medium/0015-3sum/3sum.ts create mode 100644 leetcode/medium/0015-3sum/README.md diff --git a/leetcode/medium/0015-3sum/3sum.test.ts b/leetcode/medium/0015-3sum/3sum.test.ts new file mode 100644 index 0000000..3e0aeba --- /dev/null +++ b/leetcode/medium/0015-3sum/3sum.test.ts @@ -0,0 +1,13 @@ +import { describe, it, expect } from 'vitest'; +import { threeSum } from './3sum'; + +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] + ]); + }); +}); diff --git a/leetcode/medium/0015-3sum/3sum.ts b/leetcode/medium/0015-3sum/3sum.ts new file mode 100644 index 0000000..e8cd27d --- /dev/null +++ b/leetcode/medium/0015-3sum/3sum.ts @@ -0,0 +1,44 @@ +/** + * + * What algorithmic pattern applies to this problem? + * 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 + */ +export function threeSum(nums: number[]): number[][] { + const sortedArray = nums.sort((a, b) => a - b); + const result = []; + + for (let i = 0; i < sortedArray.length; i++) { + // 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--; + } else if (sum < 0) { + left++; + } else { + right--; + } + } + } + + return result; +} + +threeSum([-4, -1, -1, 0, 1, 2]); diff --git a/leetcode/medium/0015-3sum/README.md b/leetcode/medium/0015-3sum/README.md new file mode 100644 index 0000000..d9f5109 --- /dev/null +++ b/leetcode/medium/0015-3sum/README.md @@ -0,0 +1,47 @@ +# 3Sum + +**Difficulty:** ![Medium](https://img.shields.io/badge/Medium-orange) + +[View on LeetCode](https://leetcode.com/problems/3sum/) + +## Problem Description + +Given an integer array `nums`, return all the triplets `[nums[i], nums[j], nums[k]]` where `nums[i] + nums[j] + nums[k] == 0`, and the indices `i`, `j`, and `k` are all distinct. + +The output should not contain any duplicate triplets. You may return the output and the triplets in any order. + +## Examples + +**Example 1:** + +```text +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:** + +```text +Input: nums = [0,1,1] +Output: [] +Explanation: The only possible triplet does not sum up to 0. +``` + +**Example 3:** + +```text +Input: nums = [0,0,0] +Output: [[0,0,0]] +Explanation: The only possible triplet sums up to 0. +``` + +## Constraints + +- `0 <= nums.length <= 3000` +- `-10^5 <= nums[i] <= 10^5` From a76700613248564f9eb82e49d16943a736e8f5ec Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Mon, 9 Feb 2026 09:51:36 -0500 Subject: [PATCH 2/8] learn: cycle 2 - add test for no valid triplets case --- leetcode/medium/0015-3sum/3sum.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/leetcode/medium/0015-3sum/3sum.test.ts b/leetcode/medium/0015-3sum/3sum.test.ts index 3e0aeba..a713f39 100644 --- a/leetcode/medium/0015-3sum/3sum.test.ts +++ b/leetcode/medium/0015-3sum/3sum.test.ts @@ -10,4 +10,10 @@ describe('3Sum', () => { [-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([]); + }); }); From d6225a425b4621504c7b45bee77f498cec2f95a4 Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Mon, 9 Feb 2026 09:52:09 -0500 Subject: [PATCH 3/8] learn: cycle 3 - add test for all zeros case --- leetcode/medium/0015-3sum/3sum.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/leetcode/medium/0015-3sum/3sum.test.ts b/leetcode/medium/0015-3sum/3sum.test.ts index a713f39..23f7866 100644 --- a/leetcode/medium/0015-3sum/3sum.test.ts +++ b/leetcode/medium/0015-3sum/3sum.test.ts @@ -16,4 +16,10 @@ describe('3Sum', () => { 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]]); + }); }); From 34ff89d02b5c5134936ce7fdb17d4fb7327910b7 Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Mon, 9 Feb 2026 09:52:41 -0500 Subject: [PATCH 4/8] learn: cycle 4 - add test for empty array edge case --- leetcode/medium/0015-3sum/3sum.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/leetcode/medium/0015-3sum/3sum.test.ts b/leetcode/medium/0015-3sum/3sum.test.ts index 23f7866..93acf93 100644 --- a/leetcode/medium/0015-3sum/3sum.test.ts +++ b/leetcode/medium/0015-3sum/3sum.test.ts @@ -22,4 +22,10 @@ describe('3Sum', () => { 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([]); + }); }); From 3657e0fc33e228e860a97abc69fd2fd727d384bf Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Mon, 9 Feb 2026 09:53:20 -0500 Subject: [PATCH 5/8] learn: cycle 5 - add test for array with fewer than 3 elements --- leetcode/medium/0015-3sum/3sum.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/leetcode/medium/0015-3sum/3sum.test.ts b/leetcode/medium/0015-3sum/3sum.test.ts index 93acf93..fcfdb93 100644 --- a/leetcode/medium/0015-3sum/3sum.test.ts +++ b/leetcode/medium/0015-3sum/3sum.test.ts @@ -28,4 +28,10 @@ describe('3Sum', () => { 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([]); + }); }); From af2156f26d09c2cc92b6b298532c74950684aafd Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Mon, 9 Feb 2026 09:54:00 -0500 Subject: [PATCH 6/8] learn: cycle 6 - add test for all positive numbers edge case --- leetcode/medium/0015-3sum/3sum.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/leetcode/medium/0015-3sum/3sum.test.ts b/leetcode/medium/0015-3sum/3sum.test.ts index fcfdb93..0f952ea 100644 --- a/leetcode/medium/0015-3sum/3sum.test.ts +++ b/leetcode/medium/0015-3sum/3sum.test.ts @@ -34,4 +34,10 @@ describe('3Sum', () => { 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([]); + }); }); From 5e3eb9039737424ac0e6be8dab71e5e73ee762d4 Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Mon, 9 Feb 2026 09:56:24 -0500 Subject: [PATCH 7/8] learn: solve 3Sum (#15) via guided TDD --- docs/PROBLEMS.md | 2 + leetcode/medium/0015-3sum/POST_MORTEM.md | 190 +++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 leetcode/medium/0015-3sum/POST_MORTEM.md diff --git a/docs/PROBLEMS.md b/docs/PROBLEMS.md index a80db5b..93e0437 100644 --- a/docs/PROBLEMS.md +++ b/docs/PROBLEMS.md @@ -122,6 +122,7 @@ Complete list of all solved problems, organized by difficulty and topic. - [0003 - Longest Substring Without Repeating Characters](../leetcode/medium/0003-longest-substring-without-repeating-characters) ![Medium](https://img.shields.io/badge/Medium-orange) - [0011 - Container With Most Water](../leetcode/medium/0011-container-with-most-water) ![Medium](https://img.shields.io/badge/Medium-orange) +- [0015 - 3Sum](../leetcode/medium/0015-3sum) ![Medium](https://img.shields.io/badge/Medium-orange) - [0016 - 3Sum Closest](../leetcode/medium/0016-3sum-closest) ![Medium](https://img.shields.io/badge/Medium-orange) - [0018 - 4Sum](../leetcode/medium/0018-4sum) ![Medium](https://img.shields.io/badge/Medium-orange) - [0031 - Next Permutation](../leetcode/medium/0031-next-permutation) ![Medium](https://img.shields.io/badge/Medium-orange) @@ -261,6 +262,7 @@ Complete list of all solved problems, organized by difficulty and topic. ### Two Pointers - [0011 - Container With Most Water](../leetcode/medium/0011-container-with-most-water) - Medium +- [0015 - 3Sum](../leetcode/medium/0015-3sum) - Medium - [0016 - 3Sum Closest](../leetcode/medium/0016-3sum-closest) - Medium - [0018 - 4Sum](../leetcode/medium/0018-4sum) - Medium - [0080 - Remove Duplicates from Sorted Array II](../leetcode/medium/0080-remove-duplicates-from-sorted-array-ii) - Medium diff --git a/leetcode/medium/0015-3sum/POST_MORTEM.md b/leetcode/medium/0015-3sum/POST_MORTEM.md new file mode 100644 index 0000000..5a1d023 --- /dev/null +++ b/leetcode/medium/0015-3sum/POST_MORTEM.md @@ -0,0 +1,190 @@ +# Post-Mortem Log + +## Problem + +Find all unique triplets in an integer array that sum to zero. The challenge is to avoid duplicate triplets in the result while efficiently searching through potentially large arrays. + +- **Problem Name:** 3Sum +- **Problem Link:** [LeetCode #15 - 3Sum](https://leetcode.com/problems/3sum/) +- **Date:** 2026-02-09 + +--- + +## Time Tracking + +- **Time to design the algorithm:** ~15 minutes (through pattern checkpoint and guided questions) +- **Time to code:** ~20 minutes (6 TDD cycles with iterations) +- **Time debugging/fixing:** ~10 minutes (understanding nested loop structure and duplicate handling) + +--- + +## Solution Exploration + +### What approaches did I consider? + +1. **Brute Force (O(n³))**: Three nested loops checking every possible triplet combination. Would work but too slow for large inputs (n ≤ 3000). + +2. **Hash Map Approach (O(n²))**: For each pair, check if the complement exists in a hash set. More complex duplicate handling required. + +3. **Sort + Two Pointers (O(n² + n log n) = O(n²))**: **Chosen approach**. Sort the array first, then for each element, use two pointers to find pairs that sum to the target. Sorting enables efficient duplicate skipping. + +### Final Solution Analysis + +- **Time Complexity:** O(n²) + - 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²) + +- **Space Complexity:** O(1) + - Not counting the output array, only using constant extra space (pointers and sum variable) + - If counting output: O(k) where k is the number of triplets found + +- **Is it optimal?** Yes. This is the optimal solution for the 3Sum problem. We cannot do better than O(n²) because we must examine at least n² pairs to find all valid triplets. + +### Why this approach worked (or didn't) + +The sort + two-pointers approach is optimal because: + +- **Sorting enables systematic search**: After sorting, we can use the two-pointer technique to efficiently search for pairs +- **Early termination**: If `nums[i] > 0`, we can stop early (all remaining numbers are positive) +- **Duplicate avoidance**: In a sorted array, duplicates are adjacent, making them easy to skip +- **No extra space**: Unlike hash map approaches, we only use O(1) extra space + +--- + +## Pattern Recognition + +### What algorithmic pattern does this problem use? + +**Two Pointers (Opposite Direction)** with a fixed element. More specifically, this uses the "Fixed Element + Two Pointers" variant: + +- Outer loop fixes one element at position `i` +- Inner loop uses two pointers (`left` and `right`) to search the remaining sorted subarray for pairs that sum to `-nums[i]` + +### Key Insight + +The "aha moment" for 3Sum is realizing it's **not** a single two-pointer problem, but a **nested structure**: **3Sum = TwoSum repeated n times**. + +For each fixed element `nums[i]`, we're essentially solving the TwoSum problem on the remaining array: "Find two numbers that sum to `-nums[i]`". Because the array is sorted, we can use the efficient two-pointer technique for each TwoSum subproblem. + +**Duplicate handling insight**: "No duplicate triplets" means no duplicate _arrays_ in the result, not no duplicate _values_ within a triplet. We handle this by skipping consecutive duplicate values for the fixed element `i`. + +### Related Problems + +1. **LeetCode #1 - Two Sum**: The foundation - finding two numbers that sum to a target +2. **LeetCode #16 - 3Sum Closest**: Similar structure, but finding the triplet closest to a target instead of exactly zero +3. **LeetCode #18 - 4Sum**: Extends the pattern to four numbers (adds another outer loop) +4. **LeetCode #167 - Two Sum II (Sorted Array)**: The two-pointer technique used in the inner loop + +--- + +## Edge Cases & Verification + +### What clarifying questions did I ask? + +(In coach mode, the pattern checkpoint questions served as clarifying questions) + +- What pattern applies? (Two pointers with nested structure) +- What variables are needed? (i, left, right, result array) +- What invariant must hold? (Systematic search of sorted space) + +### Edge cases handled + +- ✅ **Empty array**: Returns `[]` +- ✅ **Array with fewer than 3 elements**: Returns `[]` +- ✅ **All zeros**: Returns `[[0, 0, 0]]` +- ✅ **No valid triplets**: Returns `[]` (e.g., `[0, 1, 1]` or all positive numbers) +- ✅ **Duplicate values in triplets**: Correctly includes triplets like `[-1, -1, 2]` +- ✅ **Duplicate triplets**: Correctly avoids adding the same triplet twice by skipping duplicate `i` values + +### Edge cases missed + +- ⚠️ **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) + +--- + +## Mistakes & Bugs + +### Mistakes I keep making + +1. **Misunderstanding "no duplicates"**: Initially confused "no duplicate triplets" with "no duplicate values within a triplet" +2. **Pointer initialization**: Started with `left = 1` instead of `left = i + 1`, causing incorrect results when `i > 0` +3. **Breaking too early**: Used `break` after finding one match, stopping the search prematurely instead of continuing to find all valid triplets +4. **Single-pass misunderstanding**: Tried to solve with a single two-pointer scan instead of nested structure (outer loop + inner two-pointer) + +### Bugs to add to the Bug List + +- **Bug**: Initializing pointers outside the loop and never resetting them + - **Fix**: Reset `left = i + 1` and `right = nums.length - 1` for each iteration of `i` +- **Bug**: Using `break` instead of moving both pointers after finding a match + - **Fix**: After finding a match, do `left++` and `right--` to continue searching +- **Bug**: Checking for duplicate values within a triplet instead of duplicate triplet arrays + - **Fix**: Skip duplicate `i` values with `if (i > 0 && nums[i] === nums[i-1]) continue` + +--- + +## Retrospective + +### Key Takeaways & Lessons Learned + +1. **Nested two-pointer pattern**: Some problems require combining patterns - 3Sum is "fixed element + two pointers", not just "two pointers" + +2. **Sorting as preprocessing**: Sorting (O(n log n)) is often worth it if it enables a more efficient main algorithm (O(n²) vs O(n³)) + +3. **Read requirements carefully**: "No duplicate triplets" ≠ "no duplicate values" - understanding the exact requirement is crucial + +4. **TDD reveals understanding gaps**: The iterative testing process exposed conceptual misunderstandings early (like pointer initialization and loop structure) + +5. **Coach mode effectiveness**: Writing code yourself (vs watching the Navigator) forces deeper understanding of the pattern + +### Add to Cheat Sheet + +**Two Pointers (Fixed Element + Search) Pattern:** + +```typescript +// Sort first +nums.sort((a, b) => a - b); + +for (let i = 0; i < nums.length; i++) { + // Skip duplicates for fixed element + if (i > 0 && nums[i] === nums[i - 1]) continue; + + let left = i + 1; + let right = nums.length - 1; + + while (left < right) { + const sum = nums[i] + nums[left] + nums[right]; + if (sum === target) { + // Found match - move BOTH pointers + left++; + right--; + } else if (sum < target) { + left++; + } else { + right--; + } + } +} +``` + +**Key principles:** + +- Reset pointers for each fixed element +- Move both pointers after finding a match +- Skip adjacent duplicates in sorted array + +--- + +## Rubric Self-Rating (1–5) + +| Category | Rating | Notes | +| ----------------------- | ------ | ----------------------------------------------------------------------------------------------------------- | +| **Problem solving** | 3/5 | Eventually identified the pattern, but struggled with nested structure initially. Needed Level 2 hints. | +| **Coding** | 4/5 | Code is clean and correct after iterations. Good variable names. Minor issues with pointer initialization. | +| **Verification** | 4/5 | Comprehensive test coverage (6 tests). Caught edge cases. Could have desk-checked logic earlier. | +| **Communication** | 3/5 | Pattern checkpoint answers showed partial understanding. Improved after guidance. Good code comments. | +| **Complexity Analysis** | 3/5 | Correctly identified O(n²) time. Space complexity explanation could be clearer (said O(n²), actually O(1)). | + +--- From ff357cc967dd049a128814c778beb26832a32b6c Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Mon, 9 Feb 2026 10:03:54 -0500 Subject: [PATCH 8/8] fix: add duplicate skipping for left/right pointers to prevent duplicate triplets --- leetcode/medium/0015-3sum/3sum.test.ts | 6 ++++++ leetcode/medium/0015-3sum/3sum.ts | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/leetcode/medium/0015-3sum/3sum.test.ts b/leetcode/medium/0015-3sum/3sum.test.ts index 0f952ea..44cb621 100644 --- a/leetcode/medium/0015-3sum/3sum.test.ts +++ b/leetcode/medium/0015-3sum/3sum.test.ts @@ -40,4 +40,10 @@ describe('3Sum', () => { const result = threeSum(nums); expect(result).toEqual([]); }); + + it('should return multiple arrays with same value', () => { + const nums = [1, 2, 0, 1, 0, 0, 0, 0]; + const result = threeSum(nums); + expect(result).toEqual([[0, 0, 0]]); + }); }); diff --git a/leetcode/medium/0015-3sum/3sum.ts b/leetcode/medium/0015-3sum/3sum.ts index e8cd27d..e672dc4 100644 --- a/leetcode/medium/0015-3sum/3sum.ts +++ b/leetcode/medium/0015-3sum/3sum.ts @@ -30,6 +30,14 @@ export function threeSum(nums: number[]): number[][] { 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--; + } } else if (sum < 0) { left++; } else {