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:
struct Node {
int value;
int count;
struct Node* parent;
struct Node* next;
struct Node* first_child;
};
When you build it, your bottom layer will look like:
1 <-> 2 <-> 3 <-> ... <-> n
with first_child
being null, parent
being a pointer to the next level up, and count
being 1.
Your second layer will look like:
1 <-> 3 <-> 5 <-> ... <-> (n-1 or n as appropriate)
with the first_child
pointing to the same value in the layer below, your parent
pointing to the next layer up, and count
being 2.
Your third layer will look like:
1 <-> 5 <-> 9 <-> 13 <-> ... <-> (something from n-3 to n)
with the first_child
pointing to the same value in the layer below, your parent
pointing to the next layer up, and count
being 4.
And so on until your very top layer just contains 1
and the next value is off the end of the array. There will be O(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:
void remove (Node* node) {
if (0 == --node->count) {
/* Actually delete */
if (node->prev != null) {
node->prev->next = node->next;
}
if (node->next != null) {
node->next.prev = node->prev;
}
if (node->parent != null && node = node->parent.first_child) {
node->parent->first_child = node->next;
node->parent->value = node->next->value;
}
}
if (node->parent != null) {
remove(node->parent);
}
if (0 == node->count) {
free(node);
}
}
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.
Node* last_in_block (Node* node) {
if (node->next == null) {
/* Slide down the end. */
while (node->first_child != null) {
node = node->first_child;
}
}
else {
while (node->first_child != null) {
Node* child = node->first_child;
while (child->next->parent == node) {
child = child->next;
}
node = child;
}
}
return 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 move
as "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.
Node* move (Node* node, int steps) {
if (node->next == null) {
/* We are at the end, go to the top */
while (node->parent != null) {
node = node->parent;
}
/* Go around the loop as many times as needed. */
steps = steps % node->count;
/* Travel down to the right level to continue our journey */
while (steps < node->count) {
node = node->first_child;
}
/* And step over this node */
steps -= node->count;
}
if (steps <= 0) {
/* EXIT HERE IF JOURNEY IS ENDED */
return last_in_block(node);
}
/* Right now the following are true.
1. We are not at the end.
2. We are also not at the top level (because the node there is at the end).
3. node->next is also not at the top level (because it is at our level).
4. 0 < steps, so traveling down we will eventually find a doable step.
*/
if (steps <= node->next->count) {
/* travel forward, then down to the right level. */
node = node->next;
while (steps < node->count) {
node = node->first_child;
}
}
else if (node->parent != node->next->parent && node->next->parent->count < steps) {
/* travel forward and then up */
node = node->next;
while (node->parent != null && node->parent->count < steps) {
node = node->parent;
}
}
else {
/* just travel forward */
node = node->next;
}
/* And now we step over the block represented by this node. */
steps -= node->count;
return move(node, steps);
}
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.