In the Josephus problem we have n people numbered from 1 to n. Each turn you skip k people and kill the next one. Usually you print the last survivor put I am interested on the sequence of victims. I tried to do this with circular linked lists based on advice found online. My algorithm still isn't fast enough when inputs are large like n=123456; k=1000000000. My time goal is under a second. I think the time complexity is O(nk), since all the linked list operations are O(1) and for each person I have to print their value and possibly move k steps. Should I find some different strategy are am I missing some obvious optimization techniques?
#include <vector>
using namespace std;
struct Node {
int value;
struct Node* next;
};
Node* insertToEmpty(struct Node* last, int x) {
Node* temp = (struct Node*)malloc(sizeof(struct Node));
temp->value = x;
last = temp;
last->next = last;
return last;
}
Node* insert(struct Node* last, int x) {
if (last == NULL) {
return insertToEmpty(last, x);
}
Node* temp = (struct Node*)malloc(sizeof(struct Node));
temp->value = x;
temp->next = last->next;
last->next = temp;
last = temp;
return last;
}
Node* delete2(struct Node* prev, struct Node* current) {
prev->next = current->next;
return current->next;
}
int main() {
int n;
int k;
cin >> n;
cin >> k;
if (n == 1) {
cout << 1;
}
else {
struct Node* curr;
struct Node* prev;
struct Node* last = NULL;
for (int i = 1; i <= n; i++) {
last = insert(last, i);
}
curr = last->next;
prev = curr;
for (int i = 1; i <= k%n; i++) {
prev = curr;
curr = curr->next;
}
do {
cout << curr->value << " ";
curr = delete2(prev, curr);
n--;
for (int j = 0; j < k%n; j++) {
prev = curr;
curr = curr->next;
}
} while (n > 1);
cout << curr->value << endl;
}
}
You are correct that using a simple linked list is
O(nk)and you have to use a better data structure if you want to speed it up. I would recommend that you use a variant of a skip list for this problem.A skip-list is a 2-dimensional structure. All of the values are at the bottom level. Each level up only has some of the values. It skips the others. If you want to move a fixed number of steps, you walk forward and up, skipping over elements, until you can walk forward and down to the exact right spot. All operations are logarithmic. In your case that will make the algorithm
O(n (log(n) + log(k))With this structure, a node will look something like this:
When you build it, your bottom layer will look like:
with
first_childbeing null,parentbeing a pointer to the next level up, andcountbeing 1.Your second layer will look like:
with the
first_childpointing to the same value in the layer below, yourparentpointing to the next layer up, andcountbeing 2.Your third layer will look like:
with the
first_childpointing to the same value in the layer below, yourparentpointing to the next layer up, andcountbeing 4.And so on until your very top layer just contains
1and the next value is off the end of the array. There will beO(log(n))levels.OK, that is setup. How do we remove something? I'm going to just write untested code in a language I don't know, please pardon the syntax errors:
This operation takes
O(log(n))time because that is how many levels there are.Next we will need a small helper function to find the last node in a block. Which means that we want to travel down and right until we get to that node.
Now what about moving forward some number of steps? That's a little tricky.
First let's agree that being at a particular spot means "I just visited here and am moving on." So we can recursively define
moveas "look for node to step over, and then subtract its count from the block". But there are a few edge cases. Again untested code, but the following should give the general idea of what I mean.And now with this data structure you can figure out how to move forward, remove the spot you are standing on, move forward again, etc.