|
| 1 | +# Four Number Sum |
| 2 | +# 🔴 Hard |
| 3 | +# |
| 4 | +# https://www.algoexpert.io/questions/four-number-sum |
| 5 | +# |
| 6 | +# Tags: Array - Sorting |
| 7 | + |
| 8 | +import timeit |
| 9 | +from collections import defaultdict |
| 10 | + |
| 11 | + |
| 12 | +# The brute force solution explores all options using nested loops. |
| 13 | +# |
| 14 | +# Time complexity: O(n^4) - Four levels deep nested loops. |
| 15 | +# Space complexity: O(1) - Constant space is used. |
| 16 | +class BruteForce: |
| 17 | + def fourNumberSum(self, array, targetSum): |
| 18 | + res = [] |
| 19 | + for i in range(len(array) - 3): |
| 20 | + for j in range(i + 1, len(array) - 2): |
| 21 | + for k in range(j + 1, len(array) - 1): |
| 22 | + for m in range(k + 1, len(array)): |
| 23 | + vals = [array[i], array[j], array[k], array[m]] |
| 24 | + if sum(vals) == targetSum: |
| 25 | + res.append(vals) |
| 26 | + return res |
| 27 | + |
| 28 | + |
| 29 | +# Use a hashmap of pair sums to find target quadruplets in O(n^2) time. |
| 30 | +# |
| 31 | +# Time complexity: O(n^2) - Nested loops with constant time operations |
| 32 | +# in the inner loop. |
| 33 | +# Space complexity: O(n^2) - The hashmap will hold the sum of all pairs. |
| 34 | +class Solution: |
| 35 | + def fourNumberSum(self, array, targetSum): |
| 36 | + # Use a hashmap of pair sums to the pairs that add up to them. |
| 37 | + # Use a set to store quadruplets to eliminate duplicates. The OJ |
| 38 | + # accepts a set of tuples as the result. |
| 39 | + sums, res = defaultdict(set), set() |
| 40 | + # Iterate over all pairs in O(n^2). |
| 41 | + for i in range(len(array) - 1): |
| 42 | + for j in range(i + 1, len(array)): |
| 43 | + vals = (array[i], array[j]) |
| 44 | + s = array[i] + array[j] |
| 45 | + if targetSum - s in sums: |
| 46 | + for pair in sums[targetSum - s]: |
| 47 | + if array[i] not in pair and array[j] not in pair: |
| 48 | + res.add( |
| 49 | + tuple( |
| 50 | + sorted( |
| 51 | + [pair[0], pair[1], array[i], array[j]] |
| 52 | + ) |
| 53 | + ) |
| 54 | + ) |
| 55 | + sums[s].add(vals) |
| 56 | + return res |
| 57 | + |
| 58 | + |
| 59 | +def test(): |
| 60 | + executors = [ |
| 61 | + BruteForce, |
| 62 | + Solution, |
| 63 | + ] |
| 64 | + tests = [ |
| 65 | + [[1, 2, 3, 4, 5, 6, 7], 10, [[1, 2, 3, 4]]], |
| 66 | + [[7, 6, 4, -1, 1, 2], 16, [[7, 6, 4, -1], [7, 6, 1, 2]]], |
| 67 | + [ |
| 68 | + [5, -5, -2, 2, 3, -3], |
| 69 | + 0, |
| 70 | + [[-5, -3, 3, 5], [-5, -2, 2, 5], [-3, -2, 2, 3]], |
| 71 | + ], |
| 72 | + ] |
| 73 | + for executor in executors: |
| 74 | + start = timeit.default_timer() |
| 75 | + for _ in range(1): |
| 76 | + for col, t in enumerate(tests): |
| 77 | + sol = executor() |
| 78 | + result = sol.fourNumberSum(t[0], t[1]) |
| 79 | + # The result could be in any order. |
| 80 | + result = sorted(map(sorted, result)) |
| 81 | + exp = sorted(map(sorted, t[2])) |
| 82 | + assert result == exp, ( |
| 83 | + f"\033[93m» {result} <> {exp}\033[91m for" |
| 84 | + + f" test {col} using \033[1m{executor.__name__}" |
| 85 | + ) |
| 86 | + stop = timeit.default_timer() |
| 87 | + used = str(round(stop - start, 5)) |
| 88 | + cols = "{0:20}{1:10}{2:10}" |
| 89 | + res = cols.format(executor.__name__, used, "seconds") |
| 90 | + print(f"\033[92m» {res}\033[0m") |
| 91 | + |
| 92 | + |
| 93 | +test() |
0 commit comments