Skip to content

Commit e946cac

Browse files
authored
Merge pull request kodecocodes#497 from remlostime/3sum-4sum
3sum 4sum
2 parents 1f970f8 + 3c0612a commit e946cac

File tree

6 files changed

+303
-0
lines changed

6 files changed

+303
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
extension Collection where Element: Equatable {
3+
4+
/// In a sorted collection, replaces the given index with a successor mapping to a unique element.
5+
///
6+
/// - Parameter index: A valid index of the collection. `index` must be less than `endIndex`
7+
func formUniqueIndex(after index: inout Index) {
8+
var prev = index
9+
repeat {
10+
prev = index
11+
formIndex(after: &index)
12+
} while index < endIndex && self[prev] == self[index]
13+
}
14+
}
15+
16+
extension BidirectionalCollection where Element: Equatable {
17+
18+
/// In a sorted collection, replaces the given index with a predecessor that maps to a unique element.
19+
///
20+
/// - Parameter index: A valid index of the collection. `index` must be greater than `startIndex`.
21+
func formUniqueIndex(before index: inout Index) {
22+
var prev = index
23+
repeat {
24+
prev = index
25+
formIndex(before: &index)
26+
} while index > startIndex && self[prev] == self[index]
27+
}
28+
}
29+
30+
func threeSum<T: BidirectionalCollection>(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable {
31+
let sorted = collection.sorted()
32+
var ret: [[T.Element]] = []
33+
var l = sorted.startIndex
34+
35+
ThreeSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) }
36+
var m = sorted.index(after: l)
37+
var r = sorted.index(before: sorted.endIndex)
38+
39+
TwoSum: while m < r && r < sorted.endIndex {
40+
let sum = sorted[l] + sorted[m] + sorted[r]
41+
if sum == target {
42+
ret.append([sorted[l], sorted[m], sorted[r]])
43+
sorted.formUniqueIndex(after: &m)
44+
sorted.formUniqueIndex(before: &r)
45+
} else if sum < target {
46+
sorted.formUniqueIndex(after: &m)
47+
} else {
48+
sorted.formUniqueIndex(before: &r)
49+
}
50+
}
51+
}
52+
53+
return ret
54+
}
55+
56+
// Answer: [[-1, 0, 1], [-1, -1, 2]]
57+
threeSum([-1, 0, 1, 2, -1, -4], target: 0)
58+
59+
// Answer: [[-1, -1, 2], [-1, 0, 1]]
60+
threeSum([-1, -1, -1, -1, 2, 1, -4, 0], target: 0)
61+
62+
// Answer: [[-1, -1, 2]]
63+
threeSum([-1, -1, -1, -1, -1, -1, 2], target: 0)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='ios'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
extension Collection where Element: Equatable {
2+
3+
/// In a sorted collection, replaces the given index with a successor mapping to a unique element.
4+
///
5+
/// - Parameter index: A valid index of the collection. `index` must be less than `endIndex`
6+
func formUniqueIndex(after index: inout Index) {
7+
var prev = index
8+
repeat {
9+
prev = index
10+
formIndex(after: &index)
11+
} while index < endIndex && self[prev] == self[index]
12+
}
13+
}
14+
15+
extension BidirectionalCollection where Element: Equatable {
16+
17+
/// In a sorted collection, replaces the given index with a predecessor that maps to a unique element.
18+
///
19+
/// - Parameter index: A valid index of the collection. `index` must be greater than `startIndex`.
20+
func formUniqueIndex(before index: inout Index) {
21+
var prev = index
22+
repeat {
23+
prev = index
24+
formIndex(before: &index)
25+
} while index > startIndex && self[prev] == self[index]
26+
}
27+
}
28+
29+
func fourSum<T: BidirectionalCollection>(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable {
30+
let sorted = collection.sorted()
31+
var ret: [[T.Element]] = []
32+
33+
var l = sorted.startIndex
34+
FourSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) }
35+
var ml = sorted.index(after: l)
36+
37+
ThreeSum: while ml < sorted.endIndex { defer { sorted.formUniqueIndex(after: &ml) }
38+
var mr = sorted.index(after: ml)
39+
var r = sorted.index(before: sorted.endIndex)
40+
41+
TwoSum: while mr < r && r < sorted.endIndex {
42+
let sum = sorted[l] + sorted[ml] + sorted[mr] + sorted[r]
43+
if sum == target {
44+
ret.append([sorted[l], sorted[ml], sorted[mr], sorted[r]])
45+
sorted.formUniqueIndex(after: &mr)
46+
sorted.formUniqueIndex(before: &r)
47+
} else if sum < target {
48+
sorted.formUniqueIndex(after: &mr)
49+
} else {
50+
sorted.formUniqueIndex(before: &r)
51+
}
52+
}
53+
}
54+
}
55+
return ret
56+
}
57+
58+
// answer: [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
59+
fourSum([1, 0, -1, 0, -2, 2], target: 0)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='ios'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>

3Sum and 4Sum/README.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# 3Sum and 4Sum
2+
3+
3Sum and 4Sum are extensions of a popular algorithm question, the [2Sum][5].
4+
5+
## 3Sum
6+
7+
> Given an array of integers, find all subsets of the array with 3 values where the 3 values sum up to a target number.
8+
>
9+
> **Note**: The solution subsets must not contain duplicate triplets.
10+
>
11+
> For example, given the array [-1, 0, 1, 2, -1, -4], and the target **0**:
12+
> The solution set is: [[-1, 0, 1], [-1, -1, 2]] // The two **-1** values in the array are considered to be distinct
13+
14+
There are 2 key procedures in solving this algorithm. Sorting the array, and avoiding duplicates.
15+
16+
### Sorting
17+
18+
Sorting your input array allows for powerful assumptions:
19+
20+
* duplicates are always adjacent to each other
21+
* moving an index to the right increases the value, while moving an index to the left decreases the value
22+
23+
You'll make use of these two rules to create an efficient algorithm.
24+
25+
### Avoiding Duplicates
26+
27+
Since you pre-sort the array, duplicates will be adjacent to each other. You just need to skip over duplicates by comparing adjacent values:
28+
29+
```
30+
extension Collection where Element: Equatable {
31+
32+
/// In a sorted collection, replaces the given index with a successor mapping to a unique element.
33+
///
34+
/// - Parameter index: A valid index of the collection. `index` must be less than `endIndex`
35+
func formUniqueIndex(after index: inout Index) {
36+
var prev = index
37+
repeat {
38+
prev = index
39+
formIndex(after: &index)
40+
} while index < endIndex && self[prev] == self[index]
41+
}
42+
}
43+
```
44+
45+
A similar implementation is used to get the unique index *before* a given index:
46+
47+
```
48+
extension BidirectionalCollection where Element: Equatable {
49+
50+
/// In a sorted collection, replaces the given index with a predecessor that maps to a unique element.
51+
///
52+
/// - Parameter index: A valid index of the collection. `index` must be greater than `startIndex`.
53+
func formUniqueIndex(before index: inout Index) {
54+
var prev = index
55+
repeat {
56+
prev = index
57+
formIndex(before: &index)
58+
} while index > startIndex && self[prev] == self[index]
59+
}
60+
}
61+
```
62+
63+
### Assembling the Subsets
64+
65+
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]`:
66+
67+
```
68+
m -> <- r
69+
[-4, -1, -1, 0, 1, 2]
70+
 l
71+
```
72+
73+
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:
74+
75+
```
76+
func threeSum<T: BidirectionalCollection>(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable {
77+
let sorted = collection.sorted()
78+
var ret: [[T.Element]] = []
79+
var l = sorted.startIndex
80+
81+
ThreeSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) }
82+
var m = sorted.index(after: l)
83+
var r = sorted.index(before: sorted.endIndex)
84+
85+
TwoSum: while m < r && r < sorted.endIndex {
86+
let sum = sorted[l] + sorted[m] + sorted[r]
87+
if sum == target {
88+
ret.append([sorted[l], sorted[m], sorted[r]])
89+
sorted.formUniqueIndex(after: &m)
90+
sorted.formUniqueIndex(before: &r)
91+
} else if sum < target {
92+
sorted.formUniqueIndex(after: &m)
93+
} else {
94+
sorted.formUniqueIndex(before: &r)
95+
}
96+
}
97+
}
98+
99+
return ret
100+
}
101+
```
102+
103+
## 4Sum
104+
105+
> 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.
106+
>
107+
> **Note**: The solution set must not contain duplicate quadruplets.
108+
109+
### Solution
110+
111+
Foursum is a very straightforward extension to the threeSum algorithm. In threeSum, you kept track of 3 indices:
112+
113+
```
114+
m -> <- r
115+
[-4, -1, -1, 0, 1, 2]
116+
 l
117+
```
118+
119+
For fourSum, you'll keep track of 4:
120+
121+
```
122+
mr -> <- r
123+
[-4, -1, -1, 0, 1, 2]
124+
 l ml ->
125+
```
126+
127+
Here's the code for it (notice it is very similar to 3Sum):
128+
129+
```
130+
func fourSum<T: BidirectionalCollection>(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable {
131+
let sorted = collection.sorted()
132+
var ret: [[T.Element]] = []
133+
134+
var l = sorted.startIndex
135+
FourSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) }
136+
var ml = sorted.index(after: l)
137+
138+
ThreeSum: while ml < sorted.endIndex { defer { sorted.formUniqueIndex(after: &ml) }
139+
var mr = sorted.index(after: ml)
140+
var r = sorted.index(before: sorted.endIndex)
141+
142+
TwoSum: while mr < r && r < sorted.endIndex {
143+
let sum = sorted[l] + sorted[ml] + sorted[mr] + sorted[r]
144+
if sum == target {
145+
ret.append([sorted[l], sorted[ml], sorted[mr], sorted[r]])
146+
sorted.formUniqueIndex(after: &mr)
147+
sorted.formUniqueIndex(before: &r)
148+
} else if sum < target {
149+
sorted.formUniqueIndex(after: &mr)
150+
} else {
151+
sorted.formUniqueIndex(before: &r)
152+
}
153+
}
154+
}
155+
}
156+
return ret
157+
}
158+
```
159+
160+
[5]: https://github.com/raywenderlich/swift-algorithm-club/tree/master/Two-Sum%20Problem

swift-algorithm-club.xcworkspace/contents.xcworkspacedata

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)