Skip to main content

· One min read
Will Holmes

Code solution

Part 1

const fs = require('fs');

function countValues() {
const vals = fs.readFileSync('day1-input.txt', 'utf8').split('\n');
return vals.reduce((acc, val) => {
let res = [];
for (const char of val) {
if (/^\d+$/.test(char)) {
res.push(char);
}
}
return acc += parseInt(res[0] + res[res.length - 1]);
}, 0);
}

Part 2

function countValuesPart2() {
const vals = fs.readFileSync('day1-input.txt', 'utf8').split('\n');
const map = new Map([
['one', '1'],
['two', '2'],
['three', '3'],
['four', '4'],
['five', '5'],
['six', '6'],
['seven', '7'],
['eight', '8'],
['nine', '9']
]);
return vals.reduce((acc, val) => {
let first = val.match(/\d|one|two|three|four|five|six|seven|eight|nine/)[0];
let last = val.match(/.*(\d|one|two|three|four|five|six|seven|eight|nine)/)[1];

if (map.has(first)) first = map.get(first);
if (map.has(last)) last = map.get(last);

return acc += parseInt(first + last);
}, 0);
}

Key Takeaways

  1. Regex can be king for these problems.
  2. Keep it simple, the part two only asks for the first and last number, so work the soltuion around that. The regex does this by using the .* it stores all the matches at last[0] and then the last match at last[1]. I could have gone with a more iterative approach through the array but that would just include more complexity and overhead than necessary.

· One min read
Will Holmes

Code solution

Part 1

const fs = require('fs');

function countValues() {
const vals = fs.readFileSync('day1-input.txt', 'utf8').split('\n');
return vals.reduce((acc, val) => {
let res = [];
for (const char of val) {
if (/^\d+$/.test(char)) {
res.push(char);
}
}
return acc += parseInt(res[0] + res[res.length - 1]);
}, 0);
}

Part 2

function countValuesPart2() {
const vals = fs.readFileSync('day1-input.txt', 'utf8').split('\n');
const map = new Map([
['one', '1'],
['two', '2'],
['three', '3'],
['four', '4'],
['five', '5'],
['six', '6'],
['seven', '7'],
['eight', '8'],
['nine', '9']
]);
return vals.reduce((acc, val) => {
let first = val.match(/\d|one|two|three|four|five|six|seven|eight|nine/)[0];
let last = val.match(/.*(\d|one|two|three|four|five|six|seven|eight|nine)/)[1];

if (map.has(first)) first = map.get(first);
if (map.has(last)) last = map.get(last);

return acc += parseInt(first + last);
}, 0);
}

Key Takeaways

  1. Regex can be king for these problems.
  2. Keep it simple, the part two only asks for the first and last number, so work the soltuion around that. The regex does this by using the .* it stores all the matches at last[0] and then the last match at last[1]. I could have gone with a more iterative approach through the array but that would just include more complexity and overhead than necessary.

· 2 min read
Will Holmes

The problem statement

Given the head of a linked list and an integer val, remove all the nodes of the linked list that have Node.val === val, and return the new head.

Understanding the problem

Problem Statement

Given a singly linked list (i.e., each node has a next pointer only), the goal is to remove all nodes that have a value equal to a specified value.

Recursive Approach

  • The process involves recursively traversing the linked list.
  • At each step, the function makes a recursive call for the next node of the current node (head.next).

Base Case

  • The recursion terminates when the current node (head) is null. In this case, the function returns null. This base case is crucial for stopping the recursion.

Removing Matching Nodes

  • After the recursive call, the function checks if the value of the current node (head.val) equals the specified value.
  • If it does, the current node is skipped by returning head.next. This effectively removes the current node from the list as the parent node in the call stack will now point its next to head.next.
  • If the current node's value does not match value, the current node is retained by returning head.

Result

  • This approach ensures that all nodes with the value equal to value are removed from the list.
  • The list is updated in place, and the modified list is returned.

Examples:

Example 1:

diagram

Input: head = [1,2,6,3,4,5,6], val = 6

Output: [1,2,3,4,5]

Example 2:

Input: head = [], val = 1

Output: []

Example 3:

Input: head = [7,7,7,7], val = 7

Output: []

Code solution

function removeElements(head: ListNode | null, val: number): ListNode | null {
if (!head) return null;
head.next = removeElements(head.next, val);
return head.val === val ? head.next : head;
}

Key Takeaways

  1. Read the question!! I first implemented a solution for doubly linked lists and then realised it was for singly.
  2. Recursion is becoming easier to visualise the more that I do it.

· 4 min read
Will Holmes

The problem statement

You have a browser of one tab where you start on the homepage and you can visit another url, get back in the history number of steps or move forward in the history number of steps.

Implement the BrowserHistory class:

  • BrowserHistory(string homepage) Initializes the object with the homepage of the browser.
  • void visit(string url) Visits url from the current page. It clears up all the forward history.
  • string back(int steps) Move steps back in history. If you can only return x steps in the history and steps > x, you will return only x steps. Return the current url after moving back in history at most steps.
  • string forward(int steps) Move steps forward in history. If you can only forward x steps in the history and steps > x, you will forward only x steps. Return the current url after forwarding in history at most steps.

Understanding the problem

This problem is essentially testing us on our ability to implement a doubly linked list.

We need to store the following:

  1. A dummy head pointer to reference the head of the linked list when performing back operations.
  2. A dummy tail pointer to reference the tail of the linked list when performing forward operations.
  3. A current node pointer to store where we are currently at in our browser history.

Once we have these, implementing the solution is fairly straight forward.

For visiting, we just set the visit node: next = tail, prev = current node. Then update those pointers accordingly and set our current pointer to next.

For back, we just loop steps until steps is decreased to 0 OR the current item equals head.next. Update current pointer and then return the val.

Similar for forward, except just going the opposite way to tail.prev.

Overall, this is a really fun problem to solve!!

Examples:

Example:

Input:

["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back",> "back"]

[["leetcode.com"],["google.com"],["facebook.com"],["youtube.com"],[1],[1],[1],["linkedin.> com"],[2],[2],[7]]

Output:

[null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com",> "google.com","leetcode.com"]

Explanation:

  1. BrowserHistory browserHistory = new BrowserHistory("leetcode.com");
  2. browserHistory.visit("google.com"); // You are in "leetcode.com". Visit "google.com"
  3. browserHistory.visit("facebook.com"); // You are in "google.com". Visit "facebook.com"
  4. browserHistory.visit("youtube.com"); // You are in "facebook.com". Visit "youtube.com"
  5. browserHistory.back(1); // You are in "youtube.com", move back to > "facebook.com" return "facebook.com"
  6. browserHistory.back(1); // You are in "facebook.com", move back to > "google.com" return "google.com"
  7. browserHistory.forward(1); // You are in "google.com", move forward to > "facebook.com" return "facebook.com"
  8. browserHistory.visit("linkedin.com"); // You are in "facebook.com". Visit "linkedin.> com"
  9. browserHistory.forward(2); // You are in "linkedin.com", you cannot move > forward any steps.
  10. browserHistory.back(2); // You are in "linkedin.com", move back two > steps to "facebook.com" then to "google.com". return "google.com"
  11. browserHistory.back(7); // You are in "google.com", you can move back > only one step to "leetcode.com". return "leetcode.com"

Code solution

class LNode {
val: string;
prev?: LNode;
next?: LNode;

constructor(val: string) {
this.val = val;
}
}

class BrowserHistory {
private readonly head: LNode;
private readonly tail: LNode;
private curr: LNode;

constructor(homepage: string) {
this.head = new LNode("");
this.tail = new LNode("");

const home = new LNode(homepage);
home.next = this.tail;
home.prev = this.head;

this.head.next = home;
this.tail.prev = home;
this.curr = home;
}

visit(url: string): void {
const visitNode = new LNode(url);
const prev = this.curr;

visitNode.next = this.tail;
visitNode.prev = prev;

this.curr.next = visitNode;
this.tail.prev = visitNode;
this.curr = this.curr.next;
}

back(steps: number): string {
let curr = this.curr;
while (steps > 0) {
if (curr === this.head.next) break;
curr = curr?.prev!;
steps -= 1;
}

this.curr = curr!;
return curr!.val;
}

forward(steps: number): string {
let curr = this.curr;
while (steps > 0) {
if (curr === this.tail.prev) break;
curr = curr?.next!;
steps -= 1;
}

this.curr = curr!;
return curr!.val;
}
}

/**
* Your BrowserHistory object will be instantiated and called as such:
* var obj = new BrowserHistory(homepage)
* obj.visit(url)
* var param_2 = obj.back(steps)
* var param_3 = obj.forward(steps)
*/

Key Takeaways

  1. Doing the design linked list question has really helped me understand how to approach doubly linked list problems. This problem made me think about implementing a current pointer to the list which was a new technique!
  2. I solved this from start to finish with no help, given it's a medium. Quite happy with it!
  3. There is a real common pattern to solving doubly linked list questions and that shows the more I do them.

· 2 min read
Will Holmes

The problem statement

Given an array nums with n objects colored red, white, or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue.

We will use the integers 0, 1, and 2 to represent the color red, white, and blue, respectively.

You must solve this problem without using the library's sort function.

Understanding the problem

You'll notice from the problem statement and from the examples that this array is just an array of number values. The task is to make each possible value (1, 2, 3) sit next to each other. We can solve this with a bubble sort algorithm, whilst not the most optimal. It will get the job done, seeing as I have just learnt about bubble sorts.

Examples:

Example 1:

Input: nums = [2,0,2,1,1,0]

Output: [0,0,1,1,2,2]

Example 2:

Input: nums = [2,0,1]

Output: [0,1,2]

Code solution

function sortColors(nums: number[]): void {
for (let i = 0; i < nums.length; i++) {
for (let j = 0; j < nums.length - 1 - i; j++) {
if (nums[j] > nums[j + 1]) {
const tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
}
}
}
}

Key Takeaways

  1. Bubble sort is not efficient, but it was good to solve a problem learing the algo that I have recently learnt.

· 2 min read
Will Holmes

The problem statement

Given the roots of two binary trees p and q, write a function to check if they are the same or not.

Two binary trees are considered the same if they are structurally identical, and the nodes have the same value.

Understanding the problem

This is a really easy introduction to binary trees.

It's essentially asking us to traverse through the binary tree and check if the values are the same at each level we traverse until we get to the bottom.

We can solve this recursively in a DFS manner.

Examples:

Example 1:

Input: p = [1,2,3], q = [1,2,3]

Output: true

Example 2:

Input: p = [1,2], q = [1,null,2]

Output: false

Example 3:

Input: p = [1,2,1], q = [1,1,2]

Output: false

Code solution

function isSameTree(p: TreeNode | null, q: TreeNode | null): boolean {
// if the nodes are both null then it's equal
if (p === null && q === null) return true;
// if one node is null and the other isnt then they are not equal
if (p === null || q === null) return false;
// recursively traverse the values whilst checking that the current node left and right values are equal.
return (
p.val === q.val &&
isSameTree(p.left, q.left) &&
isSameTree(p.right, q.right)
);
}

Key Takeaways

  1. All the challenges that have required recursion before gave me the intuition to use it in this problem. Whilst it's not a challenging problem it's reassuring to know that i'm starting to notice patterns.

· 6 min read
Will Holmes

The problem statement

Design your implementation of the linked list. You can choose to use a singly or doubly linked list. A node in a singly linked list should have two attributes: val and next. val is the value of the current node, and next is a pointer/reference to the next node. If you want to use the doubly linked list, you will need one more attribute prev to indicate the previous node in the linked list. Assume all nodes in the linked list are 0-indexed.

Implement the MyLinkedList class:

  • MyLinkedList() Initializes the MyLinkedList object.
  • int get(int index) Get the value of the indexth node in the linked list. If the index is invalid, return -1.
  • void addAtHead(int val) Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
  • void addAtTail(int val) Append a node of value val as the last element of the linked list.
  • void addAtIndex(int index, int val) Add a node of value val before the indexth node in the linked list. If index equals the length of the linked list, the node will be appended to the end of the linked list. If index is greater than the length, the node will not be inserted.
  • void deleteAtIndex(int index) Delete the indexth node in the linked list, if the index is valid.

Understanding the problem

For this problem it's easier for me to just express the solution in code. But generally here it just wants us to do basic operations of linked list manipulation for our doubly linked list. It gets tricky with adding / deleting at a given index which is why this problem is a medium one.

Examples:

Example 1:

Input ["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]

[[], [1], [3], [1, 2], [1], [1], [1]]

Output

[null, null, null, null, 2, null, 3]

Explanation

  • MyLinkedList myLinkedList = new MyLinkedList();
  • myLinkedList.addAtHead(1);
  • myLinkedList.addAtTail(3);
  • myLinkedList.addAtIndex(1, 2); // linked list becomes 1->2->3
  • myLinkedList.get(1); // return 2
  • myLinkedList.deleteAtIndex(1); // now the linked list is 1->3
  • myLinkedList.get(1); // return 3

Code solution

class LNNode {
prev: LNNode | null;
next: LNNode | null;
val: number;

constructor(val: number) {
this.prev = null;
this.next = null;
this.val = val;
}
}

class MyLinkedList {
head: LNNode | null;
tail: LNNode | null;

constructor() {
// initialise our linkedlist with two dummy pointers. This makes deletions and insertions easier as we don't have to worry about the linkedlist being in a certain state.
this.head = new LNNode(0);
this.tail = new LNNode(0);
this.head.next = this.tail;
this.tail.prev = this.head;
}

get(index: number): number {
// loop through the linked list until we have reached our index. We do this by decrementing until index is 0.
let curr = this.head.next;
while (curr && index > 0) {
curr = curr.next;
index -= 1;
}
// We dont want to return the dummy node at this.tail, so we just make sure that the loop didn't get to that. We wont ever hit dummy head because we start at this.head.next;
if (curr && curr !== this.tail && index === 0) {
return curr.val;
}
// We haven't found our item so return -1.
return -1;
}

addAtHead(val: number): void {
const node = new LNNode(val);
const next = this.head.next;
const prev = this.head;

// set dummy next to new node
prev.next = node;
// set the first actual value's prev to new node
next.prev = node;
// set new node next to actual value
node.next = next;
// set new node prev to dummy
node.prev = prev;
}

addAtTail(val: number): void {
const node = new LNNode(val);
const next = this.tail;
const prev = this.tail.prev;

// set dummy next to new node
prev.next = node;
// set the first actual value's prev to new node
next.prev = node;
// set new node next to actual value
node.next = next;
// set new node prev to dummy
node.prev = prev;
}

addAtIndex(index: number, val: number): void {
let curr = this.head;
while (curr && index > 0) {
curr = curr.next;
index -= 1;
}
if (!curr) return;

const node = new LNNode(val);
const next = curr.next;

// set new node next to the next item from our index found node
node.next = next;
// set new node prev to the current index found node
node.prev = curr;
// set the current index found node next, to our new node.
curr.next = node;

// if curr.next is not null (exists) then set its prev pointer to our new node.
if (next) next.prev = node;
}

deleteAtIndex(index: number): void {
let curr = this.head.next;
while (curr !== this.tail && index > 0) {
curr = curr.next;
index -= 1;
}
if (!curr || curr === this.tail) return;

const prev = curr.prev;
const next = curr.next;

// set curr.prev.next = curr.next; i.e. imagine 1,2,3 we want to delete 2 so we set 1.next = 3;
if (prev) prev.next = next;
// set curr.next.prev = curr.prev; i.e. imagine 1,2,3 we want to delete 2 so we set 3.prev = 1;
if (next) next.prev = prev;
}
}

/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/

Key Takeaways

  1. Keep re-trying this one from scratch. It's a new concept and therefore a weak area of mine. I'm starting to grasp the concepts now, it's just taking some time initially as I struggle to visualise how new algos work in my head.
  2. Having done single linked lists before hand, I wasn't so much in the dark. It's just the node switching and in the relevant order that throws me off.
  3. Looping with a while loop is sensible and seems to be a running pattern with linked lists.
  4. This was my first medium question in a while. Whilst I didn't complete it on my own, I did learn a lot from it.

· 3 min read
Will Holmes

The problem statement

You are given the heads of two sorted linked lists list1 and list2.

Merge the two lists into one sorted list. The list should be made by splicing together the nodes of the first two lists.

Return the head of the merged linked list.

Understanding the problem

A few things to note;

  1. Lists are in increasing order node after node.
  2. They are both sorted lists.

We can go through either one of two routes:

  1. Iterative - use a while loop and go through the whole node list collection, storing a ref to the top via a new dummy node.
  2. Recursive - recursively traverse the lists at the same time.

Both approaches are appropriate solutions.

Examples:

Example 1 alt text

Input: list1 = [1,2,4], list2 = [1,3,4]

Output: [1,1,2,3,4,4]

Example 2:

Input: list1 = [], list2 = []

Output: []

Example 3:

Input: list1 = [], list2 = [0]

Output: [0]

Code solution

Iterative:

function mergeTwoLists(
list1: ListNode | null,
list2: ListNode | null
): ListNode | null {
// Create a placeholder head node
let head = new ListNode();
// Set the tail position to start at head
let tail = head;

// While both lists have values for us to compare
while (list1 && list2) {
// If list 1's value is less than list 2, then we want that to go first. So we set that as the next value in the tail.
if (list1.val <= list2.val) {
tail.next = list1;
list1 = list1.next;
} else {
// Else list 2's value goes next in line of the tail.
tail.next = list2;
list2 = list2.next;
}
// Set the tail to the next pointer.
tail = tail.next;
}
// One of the lists will be null by the end (or both could be) just append the leftover node at the end of the tail. (It will be a greater value as lower ones have already been added).
tail.next = list1 ? list1 : list2;

// return head.next because head.val was a dummy.
return head.next;
}

Recursive:

function mergeTwoLists(
list1: ListNode | null,
list2: ListNode | null
): ListNode | null {
// If one of the lists are null, return the other one.
if (!list1) return list2;
if (!list2) return list1;

let merged: ListNode;

// if list1.val is less than list2.val, create our head node, then set the next value as the current func but with the next list1 value and the current list2 value.
if (list1.val < list2.val) {
merged = new ListNode(list1.val, mergeTwoLists(list1.next, list2));
} else {
// Same here as above, except l2 and l1.
merged = new ListNode(list2.val, mergeTwoLists(list1, list2.next));
}

// return the end product.
return merged;
}

Key Takeaways

  1. Visualizing recursive / traversal based algorithms are something that i struggle with and need to work on (with practice).
  2. Keep re-practicing this question, it's a good one to know how to solve thoughtfully as opposed to trying to memorise solutions.

· 2 min read
Will Holmes

The problem statement

Design a stack that supports, push, pop, top, and retrieving the minimum element in constant time.

Implement the MinStack class:

  • MinStack() init's the stack object.
  • void push(int val) pushes the element val onto the stack.
  • void pop() removes the element on the top of the stack.
  • int top() gets the top element of the stack.
  • int getMin() retrieves the minimum element in the stack.

You must implement a solution with O(1) time complexity for each function.

Understanding the problem

The main thing to digest from the problem is that all operations have to be of a constant time execution O(1).

Given that we need to implement a function that is getMin() this tells us that we need to be able to get the minimum value in constant time.

Now doing this by with a reduce or some sort of filter on minimum value comparison would result in getMin() giving us a time complexity of O(n) since we could end up iterating the whole array to get min.

So this tells us we need to be able to store a reference to minimum value upon insertion into our stack.

By doing this it will allow us to fetch the top item from the stack at any point and instantly return the min value. Satisfying our problem.

All the other funcs are pretty simple to implement.

Examples:

Input

["MinStack","push","push","push","getMin","pop","top","getMin"]

[[],[-2],[0],[-3],[],[],[],[]]

Output

[null,null,null,null,-3,null,0,-2]

Explanation

  • MinStack minStack = new MinStack();
  • minStack.push(-2);
  • minStack.push(0);
  • minStack.push(-3);
  • minStack.getMin(); // return -3
  • minStack.pop();
  • minStack.top(); // return 0
  • minStack.getMin(); // return -2

Code solution

class MinStack {
private stack: { val: number; min: number }[] = [];

constructor() {}

push(val: number): void {
this.stack.push({
val,
min:
this.stack.length === 0
? val
: Math.min(this.stack[this.stack.length - 1].min, val),
});
}

pop(): void {
this.stack.pop();
}

top(): number {
return this.stack[this.stack.length - 1].val;
}

getMin(): number {
return this.stack[this.stack.length - 1].min;
}
}

Key Takeaways

  1. Math.min proves to be a very handy util yet again.
  2. Storing references to values can be a good way to achieve constant time complexity in challenges where you would typically return them in O(n) dealing with arrays / stacks.

· 4 min read
Will Holmes

The problem statement

You are keeping the scores for a baseball game with strange rules. At the beginning of the game, you start with an empty record.

You are given a list of strings operations, where operations[i] is the ith operation you must apply to the record and is one of the following:

  • An integer x.
    • Record a new score of x.
  • '+'.
    • Record a new score that is the sum of the previous two scores.
  • 'D'.
    • Record a new score that is the double of the previous score.
  • 'C'.
    • Invalidate the previous score, removing it from the record.

Return the sum of all the scores on the record after applying all the operations.

The test cases are generated such that the answer and all intermediate calculations fit in a 32-bit integer and that all operations are valid.

Understanding the problem

This problem is asking us to essentially perform the basic operations of interacting with a stack.

The instructions are pretty clear. Every test case that they run our code against is going to be valid as specified. So we don't need to create fault tolerant code.

So lets digest:

We want to store a stack of results which are numerical based upon each iteration of operations.

  • When x then we want to push that int value into the stack.
  • When + then we want to add stack[i-1] to stack[i-2] and push that int value into the stack.
  • When D we want to stack[i - 1] * 2 and push that int value into the stack.
  • When C we want to pop the last value from the stack.

Then at the end it wants us to return the sum of stack.

That's it. Pretty simple. Let's dive into the code!

Examples:

Example 1:

Input: ops = ["5","2","C","D","+"] Output: 30 Explanation:

  • "5" - Add 5 to the record, record is now [5].
  • "2" - Add 2 to the record, record is now [5, 2].
  • "C" - Invalidate and remove the previous score, record is now [5].
  • "D" - Add 2 * 5 = 10 to the record, record is now [5, 10].
  • "+" - Add 5 + 10 = 15 to the record, record is now [5, 10, 15].

The total sum is 5 + 10 + 15 = 30.

Example 2:

Input: ops = ["5","-2","4","C","D","9","+","+"] Output: 27 Explanation:

  • "5" - Add 5 to the record, record is now [5].
  • "-2" - Add -2 to the record, record is now [5, -2].
  • "4" - Add 4 to the record, record is now [5, -2, 4].
  • "C" - Invalidate and remove the previous score, record is now [5, -2].
  • "D" - Add 2 * -2 = -4 to the record, record is now [5, -2, -4].
  • "9" - Add 9 to the record, record is now [5, -2, -4, 9].
  • "+" - Add -4 + 9 = 5 to the record, record is now [5, -2, -4, 9, 5].
  • "+" - Add 9 + 5 = 14 to the record, record is now [5, -2, -4, 9, 5, 14].

The total sum is 5 + -2 + -4 + 9 + 5 + 14 = 27.

Example 3:

Input: ops = ["1","C"] Output: 0 Explanation:

  • "1" - Add 1 to the record, record is now [1].
  • "C" - Invalidate and remove the previous score, record is now [].
  • Since the record is empty, the total sum is 0.

Code solution

function calculatePoints(operations: string[]): number {
const scores: number[] = [];
for(let i = 0; i < operations.length; i++) {
switch(operations[i]) {
case: '+':
scores.push(scores[scores.length - 1] + scores[scores.length - 2]);
break;
case 'D':
scores.push(scores[scores.length - 1] * 2);
break;
case 'C':
scores.pop();
break;
default:
scores.push(parseInt(opreations[i]));
break;
}
}
return scores.reduce((acc, curr) => acc + curr, 0);
}

Key Takeaways

  1. Think of arrays like stacks. A stack is just a vertical array with the same concept of LIFO (Last in First out) via it's push/pop mechanism.