Skip to content

Commit 0efd601

Browse files
committed
Add randomized selection algorithm for k-th smallest element
1 parent 7be7b6f commit 0efd601

File tree

3 files changed

+277
-31
lines changed

3 files changed

+277
-31
lines changed

Kth Largest Element/README.markdown

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,141 @@
11
# k-th Largest Element Problem
22

3-
You're given an integer array `a`. Write an algorithm that finds the kth largest element in the array.
3+
You're given an integer array `a`. Write an algorithm that finds the *k*-th largest element in the array.
44

5-
The following solution is semi-naive. Its time complexity is O(n log n) since it first sorts the array, and uses an additional O(n) space. Better solutions using heaps exist that run in O(n) time.
5+
For example, the 1-st largest element is the maximum value that occurs in the array. If the array has *n* elements, the *n*-th largest element is the minimum. The median is the *n/2*-th largest element.
6+
7+
## The naive solution
8+
9+
The following solution is semi-naive. Its time complexity is **O(n log n)** since it first sorts the array, and uses an additional **O(n)** space. Better solutions using heaps exist that run in **O(n)** time.
610

711
```swift
812
func kthLargest(a: [Int], k: Int) -> Int? {
9-
let len = a.count
13+
let len = a.count
1014

11-
if (k <= 0 || k > len || len < 1) { return nil }
15+
if k <= 0 || k > len || len < 1 { return nil }
1216

13-
let sorted = a.sort()
17+
let sorted = a.sort()
1418

15-
return sorted[len - k]
19+
return sorted[len - k]
1620
}
1721
```
1822

19-
The `kthLargest()` function takes two parameters: the array `a` with consisting of integers, and `k`, in order to find the kth largest element. It returns the kth largest element.
23+
The `kthLargest()` function takes two parameters: the array `a` consisting of integers, and `k`. It returns the k-th largest element.
2024

2125
Let's take a look at an example and run through the algorithm to see how it works. Given `k = 4` and the array:
2226

2327
```swift
2428
[ 7, 92, 23, 9, -1, 0, 11, 6 ]
2529
```
2630

27-
Initially there's no direct way to find the kth largest element, but after sorting the array it's rather straightforward. Here's the sorted array:
31+
Initially there's no direct way to find the k-th largest element, but after sorting the array it's rather straightforward. Here's the sorted array:
2832

2933
```swift
3034
[ -1, 0, 6, 7, 9, 11, 23, 92 ]
3135
```
3236

33-
Now, all we must do is take the kth largest value, which is located at: `a[a.count - k] = a[8 - 4] = a[4] = 9`
37+
Now, all we must do is take the value at index `a.count - k`:
38+
39+
```swift
40+
a[a.count - k] = a[8 - 4] = a[4] = 9
41+
```
42+
43+
Of course, if you were looking for the k-th *smallest* element, you'd use `a[k]`.
44+
45+
## A faster solution
46+
47+
There is a clever algorithm that combines the ideas of [binary search](../Binary Search/) and [quicksort](../Quicksort/) to arrive at an **O(n)** solution.
48+
49+
Recall that binary search splits the array in half over and over again, to quickly narrow in on the value you're searching for. That's what we'll do here too.
50+
51+
Quicksort also splits up arrays. It uses partitioning to move all smaller values to the left of the pivot and all greater values to the right. After partitioning around a certain pivot, that pivot will already be in its final, sorted position. We can use that to our advantage here.
52+
53+
We choose a random pivot, partition the array around that pivot, and then act like a binary search and only continue in the left or right partition. This repeats until we've found a pivot that happens to end up in the *k*-th position.
54+
55+
Let's look at the original example again. We're looking for the 4-th largest element in this array:
56+
57+
[ 7, 92, 23, 9, -1, 0, 11, 6 ]
58+
59+
The algorithm is a bit easier to follow if we look for the k-th *smallest* item instead, so let's take `k = 4` and look for the 4-th smallest element.
60+
61+
Note that we don't have to sort the array first. We pick a random pivot, let's say `11` and partition the array around that. We might end up with something like this:
62+
63+
[ 7, 9, -1, 0, 6, 11, 92, 23 ]
64+
<------ smaller larger -->
65+
66+
As you can see, all values smaller than `11` are on the left; all values larger are on the right. The pivot value is now in its final place. The index of the pivot is 5, so the 4-th smallest element must be in the left partition somewhere. We can ignore the rest of the array from now on:
67+
68+
[ 7, 9, -1, 0, 6, x, x, x ]
69+
70+
Again let's pick a random pivot, let's say `6`, and partition the array around it. We might end up with something like this:
71+
72+
[ -1, 0, 6, 9, 7, x, x, x ]
73+
74+
Pivot `6` ended up at index 2, so obviously the 4-th smallest item must be in the right partition. We can ignore the left partition:
75+
76+
[ x, x, x, 9, 7, x, x, x ]
77+
78+
Again we pick a pivot value at random, let's say `9`, and partition the array:
79+
80+
[ x, x, x, 7, 9, x, x, x ]
81+
82+
The index of pivot `9` is 4, and that's exactly the *k* we're looking for. We're done! Notice how this only took a few steps and we did not have to sort the array first.
83+
84+
The following function implements these ideas:
85+
86+
```swift
87+
public func randomizedSelect<T: Comparable>(array: [T], order k: Int) -> T {
88+
var a = array
89+
90+
func randomPivot<T: Comparable>(inout a: [T], _ low: Int, _ high: Int) -> T {
91+
let pivotIndex = random(min: low, max: high)
92+
swap(&a, pivotIndex, high)
93+
return a[high]
94+
}
95+
96+
func randomizedPartition<T: Comparable>(inout a: [T], _ low: Int, _ high: Int) -> Int {
97+
let pivot = randomPivot(&a, low, high)
98+
var i = low
99+
for j in low..<high {
100+
if a[j] <= pivot {
101+
swap(&a, i, j)
102+
i += 1
103+
}
104+
}
105+
swap(&a, i, high)
106+
return i
107+
}
108+
109+
func randomizedSelect<T: Comparable>(inout a: [T], _ low: Int, _ high: Int, _ k: Int) -> T {
110+
if low < high {
111+
let p = randomizedPartition(&a, low, high)
112+
if k == p {
113+
return a[p]
114+
} else if k < p {
115+
return randomizedSelect(&a, low, p - 1, k)
116+
} else {
117+
return randomizedSelect(&a, p + 1, high, k)
118+
}
119+
} else {
120+
return a[low]
121+
}
122+
}
123+
124+
precondition(a.count > 0)
125+
return randomizedSelect(&a, 0, a.count - 1, k)
126+
}
127+
```
128+
129+
To keep things readable, it splits up the functionality into three inner functions:
130+
131+
- `randomPivot()` picks a random number and puts it at the end of the current partition (this is a requirement of the Lomuto partitioning scheme, see the discussion on [quicksort](../Quicksort/) for more details).
132+
133+
- `randomizedPartition()` is Lomuto's partitioning scheme from quicksort. When this completes, the randomly chosen pivot is in its final sorted position in the array. It returns the array index of the pivot.
134+
135+
- `randomizedSelect()` does all the hard work. It first calls the partitioning function and then decides what to do next. If the index of the pivot is equal to the k-th number we're looking for, we're done. If `k` is less than the pivot index, it must be in the left partition and we'll recursively try again there. Likewise for when the k-th number must be in the right partition.
136+
137+
Pretty cool, huh?
138+
139+
> **Note:** This function calculates the *k*-th smallest item in the array, where *k* starts at 0. If you want the *k*-th largest item, call it with `a.count - k`.
34140
35-
*Written by Daniel Speiser*
141+
*Written by Daniel Speiser. Additions by Matthijs Hollemans.*
Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
/*
2-
* Returns the k-th largest value inside of an array a.
3-
* This is an O(n log n) solution since we sort the array.
4-
*/
1+
//: Playground - noun: a place where people can play
2+
53
func kthLargest(a: [Int], k: Int) -> Int? {
6-
let len = a.count
7-
8-
if (k <= 0 || k > len || len < 1) { return nil }
9-
10-
let sorted = a.sort()
11-
12-
return sorted[len - k]
4+
let len = a.count
5+
if k <= 0 || k > len || len < 1 { return nil }
6+
7+
let sorted = a.sort()
8+
return sorted[len - k]
139
}
1410

11+
1512
let a = [5, 1, 3, 2, 7, 6, 4]
1613

1714
kthLargest(a, k: 0)
@@ -23,3 +20,74 @@ kthLargest(a, k: 5)
2320
kthLargest(a, k: 6)
2421
kthLargest(a, k: 7)
2522
kthLargest(a, k: 8)
23+
24+
25+
26+
27+
import Foundation
28+
29+
/* Returns a random integer in the range min...max, inclusive. */
30+
public func random(min min: Int, max: Int) -> Int {
31+
assert(min < max)
32+
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
33+
}
34+
35+
/*
36+
Swift's swap() doesn't like it if the items you're trying to swap refer to
37+
the same memory ___location. This little wrapper simply ignores such swaps.
38+
*/
39+
public func swap<T>(inout a: [T], _ i: Int, _ j: Int) {
40+
if i != j {
41+
swap(&a[i], &a[j])
42+
}
43+
}
44+
45+
public func randomizedSelect<T: Comparable>(array: [T], order k: Int) -> T {
46+
var a = array
47+
48+
func randomPivot<T: Comparable>(inout a: [T], _ low: Int, _ high: Int) -> T {
49+
let pivotIndex = random(min: low, max: high)
50+
swap(&a, pivotIndex, high)
51+
return a[high]
52+
}
53+
54+
func randomizedPartition<T: Comparable>(inout a: [T], _ low: Int, _ high: Int) -> Int {
55+
let pivot = randomPivot(&a, low, high)
56+
var i = low
57+
for j in low..<high {
58+
if a[j] <= pivot {
59+
swap(&a, i, j)
60+
i += 1
61+
}
62+
}
63+
swap(&a, i, high)
64+
return i
65+
}
66+
67+
func randomizedSelect<T: Comparable>(inout a: [T], _ low: Int, _ high: Int, _ k: Int) -> T {
68+
if low < high {
69+
let p = randomizedPartition(&a, low, high)
70+
if k == p {
71+
return a[p]
72+
} else if k < p {
73+
return randomizedSelect(&a, low, p - 1, k)
74+
} else {
75+
return randomizedSelect(&a, p + 1, high, k)
76+
}
77+
} else {
78+
return a[low]
79+
}
80+
}
81+
82+
precondition(a.count > 0)
83+
return randomizedSelect(&a, 0, a.count - 1, k)
84+
}
85+
86+
87+
randomizedSelect(a, order: 0)
88+
randomizedSelect(a, order: 1)
89+
randomizedSelect(a, order: 2)
90+
randomizedSelect(a, order: 3)
91+
randomizedSelect(a, order: 4)
92+
randomizedSelect(a, order: 5)
93+
randomizedSelect(a, order: 6)

Kth Largest Element/kthLargest.swift

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,85 @@
1+
import Foundation
2+
13
/*
2-
* Returns the k-th largest value inside of an array a.
3-
* This is an O(n log n) solution since we sort the array.
4-
*/
4+
* Returns the k-th largest value inside of an array a.
5+
* This is an O(n log n) solution since we sort the array.
6+
*/
57
func kthLargest(a: [Int], k: Int) -> Int? {
6-
let len = a.count
7-
8-
if (k <= 0 || k > len || len < 1) { return nil }
9-
10-
let sorted = a.sort()
11-
12-
return sorted[len - k]
8+
let len = a.count
9+
if k <= 0 || k > len || len < 1 { return nil }
10+
11+
let sorted = a.sort()
12+
return sorted[len - k]
13+
}
14+
15+
// MARK: - Randomized selection
16+
17+
/* Returns a random integer in the range min...max, inclusive. */
18+
public func random(min min: Int, max: Int) -> Int {
19+
assert(min < max)
20+
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
21+
}
22+
23+
/*
24+
Swift's swap() doesn't like it if the items you're trying to swap refer to
25+
the same memory ___location. This little wrapper simply ignores such swaps.
26+
*/
27+
public func swap<T>(inout a: [T], _ i: Int, _ j: Int) {
28+
if i != j {
29+
swap(&a[i], &a[j])
30+
}
31+
}
32+
33+
/*
34+
Returns the i-th smallest element from the array.
35+
36+
This works a bit like quicksort and a bit like binary search.
37+
38+
The partitioning step picks a random pivot and uses Lomuto's scheme to
39+
rearrange the array; afterwards, this pivot is in its final sorted position.
40+
41+
If this pivot index equals i, we're done. If i is smaller, then we continue
42+
with the left side, otherwise we continue with the right side.
43+
44+
Expected running time: O(n) if the elements are distinct.
45+
*/
46+
public func randomizedSelect<T: Comparable>(array: [T], order k: Int) -> T {
47+
var a = array
48+
49+
func randomPivot<T: Comparable>(inout a: [T], _ low: Int, _ high: Int) -> T {
50+
let pivotIndex = random(min: low, max: high)
51+
swap(&a, pivotIndex, high)
52+
return a[high]
53+
}
54+
55+
func randomizedPartition<T: Comparable>(inout a: [T], _ low: Int, _ high: Int) -> Int {
56+
let pivot = randomPivot(&a, low, high)
57+
var i = low
58+
for j in low..<high {
59+
if a[j] <= pivot {
60+
swap(&a, i, j)
61+
i += 1
62+
}
63+
}
64+
swap(&a, i, high)
65+
return i
66+
}
67+
68+
func randomizedSelect<T: Comparable>(inout a: [T], _ low: Int, _ high: Int, _ k: Int) -> T {
69+
if low < high {
70+
let p = randomizedPartition(&a, low, high)
71+
if k == p {
72+
return a[p]
73+
} else if k < p {
74+
return randomizedSelect(&a, low, p - 1, k)
75+
} else {
76+
return randomizedSelect(&a, p + 1, high, k)
77+
}
78+
} else {
79+
return a[low]
80+
}
81+
}
82+
83+
precondition(a.count > 0)
84+
return randomizedSelect(&a, 0, a.count - 1, k)
1385
}

0 commit comments

Comments
 (0)