Last Man Standing
August 16, 2019
Today’s exercise is a fun task for a lazy summer Friday:
Given a circular array X of positive integers and a starting index k of the array, delete the element at k and jump X[k] steps in the circular array. Repeat until only a single element remains. Find the last remaining element.
For example, with array 6 2 3 1 4 7 5 and k = 3, the last man standing is 3, computed as shown below, with the item to be deleted at each step marked with brackets and the list rotated at each step to bring the new head of the list to the front:
6 2 3 [1] 4 7 5 4 [7] 5 6 2 3 5 6 [2] 3 4 3 4 [5] 6 6 3 [4] [6] 3 3
Your task is to write a program to find the last remaining element. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
I started down the same “circular path” as @programmingpraxis but then
realized it is easier to use plain lists and modulo. Here’s the result
in standard R7RS scheme plus a couple of SRFI helpers.
Here’s an array-based solution in Python.
It would be interesting to compare the performance of an array versus linked list solution. I suppose it would depend on various factors, including how large the numbers are in the list. The following video reports interesting results for a related analysis:
Output:
There was an off-by-one error in my preceding solution (on line 6).
Here’s the update.
Output:
from collections import deque
def last_standing(seq, k):
dq = deque(seq)
while len(dq) > 1:
dq.rotate(-k)
k = dq.popleft()
Simple Python solution using a deque:
Here’s a solution that uses a binary tree (as a heap array, so that the children of node i are nodes 2i and 2i+1) to store the set of undeleted elements of the original array. Each node contains the count of undeleted elements in the corresponding subtree so we can find the nth undeleted element, and add or delete an element in log n time.
For eg. 1000000 elements, the final element is calculated in 0.5 seconds, against 2 minutes or so for a simple array shuffling solution:
And here’s another idea – use a bitset to keep track of the deleted values. See: https://stackoverflow.com/questions/7669057/find-nth-set-bit-in-an-int for a discussion of ways of finding the index of the nth set bit in an integer – we can do it very efficiently on Haswell with the the pdep instruction, or in a loop as here (using code from sth on that Stack Overflow page).
Of course, this limits the size of the array we can handle, but we could combine with the previous solution and use a binary tree with bitsets at the leaves.
Output:
Rust version using an iterator:
Link to Rust Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=12278c57ce468bb904d47fcfaefc7bc9
Rust version in the spirit of Daniel’s Python version above: