3Sum and 4Sum are extensions of a popular algorithm question, the 2Sum.
Given an array of integers, find all subsets of the array with 3 values where the 3 values sum up to a target number.
Note: The solution subsets must not contain duplicate triplets.
For example, given the array [-1, 0, 1, 2, -1, -4], and the target 0:
The solution set is: [[-1, 0, 1], [-1, -1, 2]] // The two -1 values in the array are considered to be distinct
There are 2 key procedures in solving this algorithm. Sorting the array, and avoiding duplicates.
Sorting your input array allows for powerful assumptions:
You'll make use of these two rules to create an efficient algorithm.
Since you pre-sort the array, duplicates will be adjacent to each other. You just need to skip over duplicates by comparing adjacent values:
extension Collection where Element: Equatable {
/// In a sorted collection, replaces the given index with a successor mapping to a unique element.
///
/// - Parameter index: A valid index of the collection. `index` must be less than `endIndex`
func formUniqueIndex(after index: inout Index) {
var prev = index
repeat {
prev = index
formIndex(after: &index)
} while index < endIndex && self[prev] == self[index]
}
}
A similar implementation is used to get the unique index before a given index:
extension BidirectionalCollection where Element: Equatable {
/// In a sorted collection, replaces the given index with a predecessor that maps to a unique element.
///
/// - Parameter index: A valid index of the collection. `index` must be greater than `startIndex`.
func formUniqueIndex(before index: inout Index) {
var prev = index
repeat {
prev = index
formIndex(before: &index)
} while index > startIndex && self[prev] == self[index]
}
}
You'll keep track of 3 indices to represent the 3 numbers. The sum at any given moment is array[l] + array[m] + array[r]
:
m -> <- r
[-4, -1, -1, 0, 1, 2]
l
The premise is quite straightforward (given that you're familiar with 2Sum). You'll iterate l
through the array. For every iteration, you also apply the 2Sum algorithm to elements after l
. You'll check the sum every time you moving the indices to check if you found match. Here's the algorithm:
func threeSum<T: BidirectionalCollection>(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable {
let sorted = collection.sorted()
var ret: [[T.Element]] = []
var l = sorted.startIndex
ThreeSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) }
var m = sorted.index(after: l)
var r = sorted.index(before: sorted.endIndex)
TwoSum: while m < r && r < sorted.endIndex {
let sum = sorted[l] + sorted[m] + sorted[r]
if sum == target {
ret.append([sorted[l], sorted[m], sorted[r]])
sorted.formUniqueIndex(after: &m)
sorted.formUniqueIndex(before: &r)
} else if sum < target {
sorted.formUniqueIndex(after: &m)
} else {
sorted.formUniqueIndex(before: &r)
}
}
}
return ret
}
Given an array S of n integers, find all subsets of the array with 4 values where the 4 values sum up to a target number.
Note: The solution set must not contain duplicate quadruplets.
Foursum is a very straightforward extension to the threeSum algorithm. In threeSum, you kept track of 3 indices:
m -> <- r
[-4, -1, -1, 0, 1, 2]
l
For fourSum, you'll keep track of 4:
mr -> <- r
[-4, -1, -1, 0, 1, 2]
l ml ->
Here's the code for it (notice it is very similar to 3Sum):
func fourSum<T: BidirectionalCollection>(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable {
let sorted = collection.sorted()
var ret: [[T.Element]] = []
var l = sorted.startIndex
FourSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) }
var ml = sorted.index(after: l)
ThreeSum: while ml < sorted.endIndex { defer { sorted.formUniqueIndex(after: &ml) }
var mr = sorted.index(after: ml)
var r = sorted.index(before: sorted.endIndex)
TwoSum: while mr < r && r < sorted.endIndex {
let sum = sorted[l] + sorted[ml] + sorted[mr] + sorted[r]
if sum == target {
ret.append([sorted[l], sorted[ml], sorted[mr], sorted[r]])
sorted.formUniqueIndex(after: &mr)
sorted.formUniqueIndex(before: &r)
} else if sum < target {
sorted.formUniqueIndex(after: &mr)
} else {
sorted.formUniqueIndex(before: &r)
}
}
}
}
return ret
}
Written for the Swift Algorithm Club by Kai Chen and Kelvin Lau