~/leetcode $ 15. 3-Sum

Question Link

Difficulty: Medium

For the brute force method, we can loop through all of the possible triplets in the list using 3 for loops (O(n^3) time) but that would be too inefficient.

When we think about optimising this, we can effectively reduce this to a two sum problem by fixing one of the 3 variables. Let’s say the list is [1, 2, 3, 4, 5]. Then, if we fix index 0 to be the first number, then we are doing two sum on the remaining list ([2, 3, 4, 5]) with the target to be -1 (0 minus the element at index 0). We can solve the reduced two sum using a hashmap in linear time (and using linear space), So overall, this would run in O(n^2) time.

Doing this via two sum is trickier than I initially thought, as I need to return the value (not the index) and I don’t want to have duplicate triplets. So maybe, there are more ways to optimise this…

Initially, I thought of sorting the list before hand and thinking about what that would give me:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result {};
        sort(nums.begin(), nums.end());

        // fix the first element
        for (int i = 0; i < nums.size() - 2; ++i) {
            // skip duplicate first elements
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }


            // do two sum on the remaining elements
            unordered_map<int, bool> seen {};
            int target = -nums[i];
            for (int j = i + 1; j < nums.size(); ++j) {
                if (seen.contains(target - nums[j])) {
                    // if we haven't used this yet
                    if (!seen[target - nums[j]]) {
                        result.push_back({nums[i], target - nums[j], nums[j]});
                        seen[target - nums[j]] = true;
                    }
                } else {
                    seen[nums[j]] = false;
                }
            }
        }

        return result;
    }
}

Clearly this isn’t good enough so I took a look at what the fast people did and duh you could use two pointers because the list is now sorted… Lemme think…

So I just switched the solution to the two pointer method (input array is sorted) and now we have a very nice space complexity (time complexity O(n^2) - still the same, space complexity O(n^2) -> O(1)).

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result {};
        sort(nums.begin(), nums.end());

        // fix the first element
        for (int i = 0; i < nums.size() - 2; ++i) {
            // skip duplicate first elements
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            // do two sum on the remaining elements
            // we can do the sorted version...
            int l = i + 1;
            int r = nums.size() - 1;
            int a = nums[i];
            while (l < r) {
                int b = nums[l];
                int c = nums[r];
                if (a + b + c == 0) {
                    // valid triplet
                    result.push_back({a, b, c});
                    while (l < r && nums[l] == b) {
                        ++l;
                    }
                } else if (a + b + c > 0) {
                    --r;
                } else {
                    ++l;
                }
            }
        }

        return result;
    }
};

Time Complexity: O(n^2)

Space Complexity: O(1)

Time Taken: 26m 42s