HTF do I reverse a linked list (video)

I previously made a post with some shittily drawn diagrams, trying to make sense of how linked list reversal works in JavaScript.

However, some of my diagrams were slightly inaccurate on second look, and also, for me, in order to visualise something like this properly it helps me to see it in motion.

To that end I’ve coded up a new solution, inspired by this video:

https://www.youtube.com/watch?time_continue=119&v=O0By4Zq0OFc&feature=emb_title

and filmed a shaky video in order to animate my terrible drawings. This is the code for the linked list, complete with reversal method:

class LinkedList {
  constructor(val) {
    this.head = {
      value: val,
      next: null,
    };
    this.tail = this.head;
  }

  append(val) {
    this.tail.next = {
      value: val,
      next: null,
    };
    this.tail = this.tail.next;
  }

  print() {
    const displayArray = [];
    let node = this.head;
    while (node) {
      displayArray.push(node.value);
      node = node.next;
    }
    console.log(displayArray);
  }

  reverse() {
    let current = this.head;
    let previous = null;
    let next = null;

    while (current) {
      next = current.next;
      current.next = previous;
      previous = current;
      current = next;
    }

    this.head = previous;
  }
}

const list = new LinkedList(1);
list.append(2);
list.append(3);
list.print();

list.reverse();
list.print();

And here is the reverse method visualised with the magic of video (I would watch at 2X speed if you value your time in any way):

sweet sweet animation video

Also, if you really want to figure out how this code works, I highly recommend making your own cut outs and moving them around. Beats staring at a screen.

HTF do I reverse a linked list (JavaScript edition)

This is one of those algorithms questions that makes my stomach hurt.

If you haven’t seen this before prepare to put your brain through a blender.

How do you reverse a linked list in JavaScript?

Firstly, you have to have a linked list. Luckily I have one I prepared earlier. JavaScript doesn’t have a linked list implementation, so we have to make our own:

class LinkedList {
  constructor(val) {
    this.head = {
      value: val,
      next: null,
    };
    this.tail = this.head;
  }

  append(val) {
    this.tail.next = {
      value: val,
      next: null,
    };
    this.tail = this.tail.next;
  }

  print() {
    const displayArray = [];
    let node = this.head;
    while (node) {
      displayArray.push(node.value);
      node = node.next;
    }
    console.log(displayArray);
  }
}

const list = new LinkedList(1);
list.append(2);
list.append(3);
list.append(4);
list.append(5);

list.print();

When this is run it will print [1,2,3,4,5], which under the hood looks like 1=>2=>3=>4=>5, with a reference to the start (head), and the end (tail) of the chain, or snake, or whatever we decide we want to think of our list as.

We can easily get to the first element, and we can easily get to the last element.

Cooooool. Now reverse it, turn those arrows the other way.

My first attempt at this worked quite nicely, but is wasteful with memory (a sin in computer science). Here it is:

  reverse() {
    const buffer = [];

    let node = this.head;

    while(node) {
      buffer.push(node.value);
      node = node.next;
    }

    this.head = {
      value: buffer.pop(),
      next: null,
    };

    node = this.head;

    while(buffer.length) {
      node.next = {
        value: buffer.pop(),
        next: null,
      }
      node = node.next;
    }
  }
}

So just stick all the values from the linked list into an array, then pull items off the array and rewrite our linked list from scratch!

I know, I’m a genius.

The runtime is O(n), which is good.

The space complexity is also O(n), which is less good. At this point if you are interviewing, I imagine the interviewer (if they even let you code this basic solution), will be tutting and saying things like ‘but can we do better?’, scratching their chin and gesticulating with whiteboard markers.

And it turns out that yes, there is a better solution. The bad news is that it is utterly brain melting to actually understand. If you get what this is doing straight off the bat, all power to you.

    reverse() {
      let first = this.head;
      this.tail = this.head;
      let second = first.next;

      while(second) {
        const temp = second.next;
        second.next = first;
        first = second;
        second = temp;
      }

      this.head.next = null;
      this.head = first;
    }

In my case it just made me want to cry and shout and stomp my feet and yell ‘BUT WHY DO I HAVE TO DO THIS STUPID SHIT. I’M A FRONTEND DEVELOPER, LET ME DO SOME CSS OR SOMETHING. I’LL JUST GOOGLE IT. THIS IS BULLSHIT!!!?!!’.

Obviously, we are onto a good problem. The ones that expand your brain and make you better at problem solving normally start with this sort of reaction. The key to getting gud is to gently soothe and comfort your troubled brain, and trust that given enough sustained concentration on the problem, some youtube videos, and a bit of sleep, this will start to make sense.

Let’s draw some pictures and match them to code (DISCLAIMER: These diagrams are not totally accurate… I’ve left them now though as this is useful as a record for me of how my thinking progressed. For a better illustration of how this algorithm works, see this post) :

We’re going to use a smaller list for this run through, because I can’t be arsed to draw endless diagrams.

We want to turn 1=>2=>3 into 3=>2=>1.

What

a time

to be alive.

let first = this.head;
this.tail = this.head;
let second = first.next;

Just assigning things for now, seems OK. See brain, nothing to be scared about. Right?

‘THIS IS BULLSHIT. WHAT THE FUCK ARE WE EVEN BOTHERING WITH THIS FOR. LET’S PLAY ROCKET LEAGUE’

Our diagram is in this state now:

OK. Next bit.

      while(second) {
        const temp = second.next;
        second.next = first;
        first = second;
        second = temp;
      }

This appears to be a loop. Let’s try one iteration first, and update our diagram.

second is pointing to the element with the value 2 for us currently, so it is truthy. We enter the while loop, filled with trepidation.

const temp = second.next

second.next = first

first = second

second = temp

Oooh wow, this kind of looks like progress, we swapped 2 and 1, and our variables are all pointing at sensible looking things. Apart from second, which is not pointing at the second element, and head which is just floating in the middle of the list. I think this is one of the tricky parts of linked list questions, the variable names end up not making sense mid way through the problem.

Let’s continue.

second is pointing to the element with the value 3, so it is truthy. We enter the while loop again, brimming with new found confidence.

const temp = second.next

temp gets set to second.next, which is now null.

second.next = first

first = second

second = temp

Second is null now, so we avoid the while loop this time round.

this.head.next = null

So we sever that endless loop setting head.next to null

this.head = first

We’ve set up our newly ordered linked list, now we just need to make it official by updating the head reference.

this.head = first;

Anddddd done.

I’m going to need to revisit this, but this scribbling exercise has already helped. I hope it helps you too.

Alternatively, go watch this video https://www.youtube.com/watch?time_continue=119&v=O0By4Zq0OFc&feature=emb_title as it explains the approach waaaaay better than these scribbles do. Wish I’d watched that first…

HTF do I find an element efficiently in a rotated sorted array

Also why on earth would you care about this?

because:

a) You are trying to do search on a randomly rotated sorted array which is enormous

b) You are trying to prepare for technical whiteboard tests

c) You like puzzles

b) is probably the most likely.

In my case it’s a combination of b) and c).

I almost solved this on my own. I didn’t get it quite right, but my thought processes were not totally wrong, which is a nice change.

QUESTION:

Search in Rotated Array: Given a sorted array of n integers that has been rotated an unknown number of times, write code to find an element in the array. You may assume that the array was originally sorted in increasing order.

Example

Input: find 5 in [15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14]

Output: 8 (the index of 5 in the array)

SOLUTION(S):

Firstly, you can just loop through the whole array, checking for the value.

That gets you O(n) time and O(1) space.

Drop that whiteboard marker, and stare at your interviewer with a look of pure intimidation.

Did it work?

No, they say they would like it to scale better than O(n).

Urgh. Who do they think they are.

Let’s mine the problem statement for clues:

  • Array is sorted
  • Array is rotated

Sorted you say… I think I smell a binary search coming on.

Demon/Gargoyle interviewer: ‘Excellent. Proceed minion’

If we can use binary search, we can cut the problem in half with each check, giving us a run time of O(log n)

Demon/Gargoyle interviewer: ‘CODE IT!!! MINION’

Hold up there, I haven’t got my tactics in place yet.

For binary search to work we have to be able to work on a sorted array.

Because our array has been rotated, we essentially have two sorted arrays:

[15, 16, 19, 20, 25]

[1, 3, 4, 5, 7, 10, 14]

We can do binary search on each of these individually just fine.

So if we find the ‘rotation point’ or whatever you want to call it, we can split the array and RECURSE LIKE OUR LIFE DEPENDS ON IT.

How we gonna find that rotation point?

Rotation point = ‘point in the array where the previous element is bigger than the next element’. i.e it is not sorted.

Could just loop through the array and check each element, but that erodes all of our good work, as we are just searching for an item in the array again so we go back to O(n).

Demon/Gargoyle interviewer: ‘WHAT ARE YOU THINKING MINION??? YOU MUST VERBALISE YOUR DEEPEST THOUGHTS TO ME’

Fuck this industry.

Maybe we can use recursion again/a version of binary search?

Then we can get back to cutting that problem in half. O(log n) for the win!

I have an idea. Look.

[15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14]

The middle index is 6, with a value of 3 in it.

One half of our array when split by this middle element is not sorted:

[15, 16, 19, 20, 25, 1, 3]

The other half is sorted:

[3, 4, 5, 7, 10, 14]

We can figure out which half is sorted by comparing the middle element (3) with the left and right most elements respectively.

15 is not smaller than 3, so that half is not sorted.

3 is smaller than 14, so that half is sorted.

Now we know that our ‘rotation point’ is in the left half of our array, so forget about the right half, and RECURSE.

[15, 16, 19, 20, 25, 1, 3]

Middle is 20, left half is sorted, right half is not sorted, so rotation point is in right half. RECURSE.

[20, 25, 1, 3]

middle is 1, element before it is 25. IT IS OUT OF ORDER WE HAVE FOUND OUR ROTATION POINT IN LOGARITHMIC TIME DID YOU HEAR THAT YOU IMPASSIVE GARGOYLE FUCK YOU

Suhwheet.

The recipe:

1) Find the rotation point recursively in O(log(n)) time.

2) Do binary search on both halves in O(2 * log(n)) time.

3) Profit/dance with joy at your new O(3 * log(n)) => O(log n) running time.

There are definitely optimisations that can be made here.

But I’m going to move on with my life for now. Behold:

const findRotationPoint = (array: number[], left: number, right: number) => {
  const middle = Math.floor((left + right) / 2);

  // This element is out of order
  if (array[middle - 1] > array[middle]) {
    return middle;
  }

  // If the left half is sorted
  if (array[left] <= array[middle]) {
    // Check the right half - as left half is OK
    return findRotationPoint(array, middle + 1, right);
  } else {
    // Check the left half because it is not sorted so must contain the rotation point
    return findRotationPoint(array, left, middle - 1);
  }
};

const searchRecursive = (
  array: number[],
  left: number,
  right: number,
  target: number
): number => {

  // Target not in array
  if (left > right) {
    return -1;
  }

  const middle = Math.floor((left + right) / 2);

  if (array[middle] === target) {
    // We found the target!
    return middle;
  }

  if (array[middle] < target) {
    return searchRecursive(array, middle + 1, right, target);
  } else {
    return searchRecursive(array, left, middle - 1, target);
  }
};

const search = (array: number[], target: number) => {

  // Find rotation point
  const rotationPoint = findRotationPoint(array, 0, array.length - 1);

  // Check to left of rotation point
  const leftResult = searchRecursive(array, 0, rotationPoint, target);

  if (leftResult !== -1) {
    // We found it!
    return leftResult;
  } else {
    // Check to right of rotation point
    return searchRecursive(array, rotationPoint, array.length - 1, target);
  }
};