Summary

A review of algorithm implementation in JavaScript. Adapted from the Tech Interview Handbook and the LeetCode website. If you are a JavaScript beginner, read JS Language first.

Arrays

To efficiently find indexes of two array values that sum to a target, create a map, then iterate linearly. At every index, if target - value is a map key, return the current index, and the index under the map key. Otherwise, add value as a key to the map, pointing to the current index. Because of action order at each step, the map always contains all previous values for direct lookup.


// typeof nums === "object" (array of numbers)
// typeof target === "number"

const twoSum = (nums, target) => {

    const map = new Map()

    for (let i = 0; i < nums.length; i++){
        if(map.has(target-nums[i])){
            return [i, map.get(target-nums[i])]
        }

        map.set(nums[i],i)
    }

}

To find 0 or the greatest positive difference between a number in an array and a number that appears earlier in the array, which can be analogized to buying and selling stock, initialize a low “price” to the first number in the array, and the best difference to 0. Then loop through the array. At every step, replace best if appropriate with the price at the current index minus low, then update low if the current price is lower.


// typeof prices === "object" (array of numbers)

const maxProfit = (prices) => {

    let low = prices[0]
    let best = 0

    for (let price of prices){
        best = Math.max(best, price-low)
        low = Math.min(low, price)
    }

    return best

}

The Boyer-Moore majority vote algorithm finds a majority element in an array in linear time when a majority element is guaranteed. After initializing count as 1 and val as the value at the 0 index, the rest of the array is looped. If the value at the new index is the same as val, increase count by 1, otherwise decrease count by one. If count reaches 0, increment the index by 1, set val to the value at that index, and set count to 1. After as many pairs of different values are removed as possible, the val remanining is the majority element.


// typeof nums === "object" (array of numbers)

const majorityElement = (nums) => {

    let count = 1
    let val = nums[0]

    for (let i = 1; i < nums.length; i++){
        if(nums[i] === val){
            count++
        } else {
            count--
        }

        if(!count){
            i++
            val = nums[i]
            count = 1
        }
    }

    return val

}

To see if a duplicate is in an array in linear time and linear space, loop the array, and at each step check to see if the value being processed is in a set. If it is, return true, else add the number to the set. Return false if no pairs are found in the array. Alternatively, sorting in n log(n) time allows an O(1) space solution by stepping through every adjacent pair in the sorted array and returning true if any pair matches.


// typeof nums === "object" (array of numbers)

const containsDuplicate = (nums) => {

    const seen = new Set()

    for (let num of nums){
        if (seen.has(num)) return true
        seen.add(num)
    }

    return false

}

// typeof nums === "object" (array of numbers)

const containsDuplicate = (nums) => {

    nums.sort((a,b)=>a-b)

    for (let i = 1; i < nums.length; i++){
        if (nums[i] === nums[i-1]) return true
    }

    return false

}

An array of objects which each contain start and end times for meetings can be sorted by starting times to check for overlapping times. After the sort by starting times, check if any ending time is less than an immediately previous starting time. This returns false. Else return true. It is not necessary to sort secondarily by ending times because if any indentical starting times are found, false will naturally be returned.


// typeof intervals === "object" (array of objects with start and end numbers)

const canAttendMeetings = (intervals) => 

    intervals.sort((a,b)=>a.start-b.start)

    for (let i = 1; i < intervals.length; i++){
        if (intervals[i].start < intervals[i-1].end) return false
    }

    return true

}

To move all zeros to the end of an array while preserving the order of nonzero elements, create a nextNonzeroLocation and assign the 0 index of the array, then iterate through the array with a different pointer. When this second pointer finds a nonzero, swap the value with the value at the nextNonzeroLocation and increment nextNonzeroLocation by 1. This will ensure that only 0 numbers will be “behind” the leading pointer but at or ahead of nextNonzeroLocation, so steadily adding nonzeroes to the nextNonzeroLocation will never displace a value that needs its relative order preserved.


// typeof nums === "object" (array of numbers)

const moveZeroes = (nums) => {

        let nextNonZeroLocation = 0

		for(let i=0; i < nums.length; i++){
			if(nums[i] != 0) {
				[nums[i], nums[nextNonZeroLocation]] = [nums[nextNonZeroLocation], nums[i]]
                nextNonZeroLocation++
			}
		}

        return nums

}

How to square the numbers in a non-decreasing array but keep them in sorted order? A linear time solution (as opposed to squaring each and sorting the whole list) is possible. An implementation with three pointers is relatively simple: the low pointer starts at the lowest index of the array, the high pointer starts at the highest, and an iterator i moves a loop from the last index of the base array to the first. At every step choose the larger in absolute value of the value at low and the value at high, then square it, place it at the i position, and increment low or decrement high depending on which pointer was chosen.


// typeof nums === "object" (array of numbers)

const sortedSquares = (nums) => {

	const result = []

	let low = 0
    let high = nums.length - 1

	for (let i = nums.length - 1; i > -1; i--) {
		if (Math.abs(nums[low]) >= Math.abs(nums[high])) {
			result[i] = nums[low] ** 2
			low++
		} else {
			result[i] = nums[high] ** 2
			high--
		}
	}

	return result

}

In an array of intervals that do not overlap, with the intervals themselves represented as arrays of [start, end] numbers, and sorted by start, how to add a new [start, end] by updating, merging, or inserting as appropriate? One method is to increment from the beginning of the array until the end is reached or the end value of the current array interval stops being less than the start value of the interval to be inserted. Then, again while there are array items to iterate, the interval to be inserted has its start and end values updated to be more mimumum and more maximum as appropriate, until the start of the array interval to be processed is larger than the end of the (modified or unmodifed) interval to be inserted.


// typeof intervals === "object" (array of arrays with 2 numbers)
// typeof newInterval === "object" (array with 2 numbers)

const insert = (intervals, newInterval) => {

    let i = 0

    while (i < intervals.length && intervals[i][1] < newInterval[0]) {
        i++
    }

    const sliceMark = i

    while (i < intervals.length && newInterval[1] >= intervals[i][0]) {
        newInterval[0] = Math.min(intervals[i][0], newInterval[0])
        newInterval[1] = Math.max(intervals[i][1], newInterval[1])
        i++
    }

    return intervals.slice(0, sliceMark).concat([newInterval]).concat(intervals.slice(i))

}

To find all unique triplets of numbers in an array that sum to 0, you can first sort the array in ascending order. A loop with three pointers can then help the problem. The i pointer in the definition of the loop will crawl from the 0 index to two less than the last index, while for every step in the loop, left and right pointers can be defined starting at the leftmost and rightmost extremes to the right of the i pointer. When the value at i is equal to the value at i-1, the step in the loop can be skipped (to guard against duplicate triplets), and if the value at i is ever positive, the loop can break (because the values at the three pointers will be positive). Otherwise, as long as left < right, check to see if the sum of the values at i, left, and right are 0. If the value is too low, increment left by 1, and if too high decrement right by 1. If the sum is 0, push the value, and increment past any duplicate values innerward from left and right so both pointers point to new values. Whether or not the sum is 0, check again if left < right to continue the inner loop.


// typeof nums === "object" (array of numbers)

const threeSum = (nums) => {

    nums.sort((a,b)=>a-b)

    const result = []
    for (let i = 0; i < nums.length - 2; i++){
        if (nums[i] > 0) break
        if (i > 0 && nums[i] === nums[i-1]) continue
        let left = i + 1
        let right = nums.length - 1

        while(left < right){
            const sum = nums[i] + nums[left] + nums[right]
            if (sum < 0) left += 1
            else if (sum > 0) right -= 1
            else {
                result.push([nums[i], nums[left], nums[right]])
                while(left < right && nums[left] === nums[left+1]) left++
                while(left < right && nums[right] === nums[right-1]) right--
                left++
                right--
            }
        }
    }
    
    return result

}

To find the product at each array location of every value in the array except the value at that location, a linear time approach with two passes involves computing the rolling product forwards of every value up to that at a given index, and then rolling over the same array backwards, multiplying the value given in the first pass by a backwardsRollingProduct of every value above the given index. Make sure to update the backwardsRollingProduct only by multiplying from the original array (though it should be used at every step to update the target value in the final array).



const productExceptSelf = (nums) => {

    const result = []
    result[0] = 1

    for (let i = 1; i < nums.length; i++) {
        result[i] = nums[i - 1] * result[i - 1]
    }

    let backwardsRollingProduct = 1
    for (let i = nums.length - 1; i > -1; i--) {
        result[i] *= backwardsRollingProduct
        backwardsRollingProduct *= nums[i]
    }

    return result

}

From an array of integers, how to return all unique combinations to a given sum? After creating a results array, this problem can be solved in a exhaustive recursive manner. You can use helper function go with parameters of remaining array items (starting with all of them), a remaining target (starting with the original target), and an array with a possible valid combination summing to the target (starting as an empty array). Inside the helper function, if target is negative, return immediately, and if target is 0, push the third parameter’s combination into the results array before returning. Else, loop the candidates in the first parameter, running a new go call at each step with the first parameter slicing the candidates at the index of the loop, the second parameter reducing target by the value at the index in the loop, and third parameter adding the current value to the possible valid combination. This method can use a given number as many times as is helpful.


// typeof candidates === "object" (array of numbers)
// typeof target === "number"

combinationSum = (candidates, target) => {
    
    const results = []

    const go = (remainingCandidates, remainingTarget, possibleCombination) => {
        if (remainingTarget < 0) return
        if (remainingTarget === 0){
            results.push(possibleCombination)
            return
        }
        for(let i = 0; i < remainingCandidates.length; i++){
            go(remainingCandidates.slice(i),remainingTarget-remainingCandidates[i],[...possibleCombination,remainingCandidates[i]])
        }
    }
    
    go(candidates,target,[])
    return results

}

How to merge intervals with two numbers like [start, end] in a larger array of such pairs? If the array has a length, you can sort the array by each start. Next, you can seed a result array with the first item in the original array. A loop through the remaining items in the original array has two cases. If the end of the last item in the result array is greater or equal to the start of the item being processed in the loop, replace the end of the last item in the result array by the greater of itself or the end of the item being processed in the loop. If there is no overlap, just push the interval being processed by the loop.


// typeof intervals === "object" (array of arrays with 2 numbers)
const merge = (intervals) => {

    if(!intervals.length) return []
    intervals.sort((a,b)=> a[0] - b[0])
    const result = [intervals[0]]

    for(let i = 1; i < intervals.length; i++){
        if(result[result.length-1][1] >= intervals[i][0]){
            result[result.length-1][1] = Math.max(intervals[i][1],result[result.length-1][1])
        } else {
            result.push(intervals[i])
        } 
    }

    return result

}

In an array where red objects are represented as 0, white objects are represented as 1, and blue objects are represented as 2, how to sort in-place so that red comes before white which comes before blue? Declare a nextRedLocation pointing to the 0 index and a nextBlueLocation pointing to the last valid index. Then loop the array. While the loop index is less than or equal to nextBlueLocation, check if the value at the index is 0. If so, swap the value at the current location with the value at the nextRedLocation and move up nextRedLocation by 1. Next, in the same space in the loop, check if the value at the index is 2. If so, swap the value with that at the nextBlueLocation, decrease nextBlueLocation by 1, and decrease the loop index by 1 in case the value moved needs transport as a red.


// typeof nums === "object" (array of numbers)

const sortColors = (nums) => {

    let nextRedLocation = 0
    let nextBlueLocation = nums.length - 1

    for (let i = 0; i <= nextBlueLocation; i++){

        if (nums[i] === 0){
            [nums[nextRedLocation], nums[i]] = [nums[i],nums[nextRedLocation]]
            nextRedLocation++
        }
        if (nums[i] === 2){
            [nums[nextBlueLocation], nums[i]] = [nums[i], nums[nextBlueLocation]]
            nextBlueLocation--
            i--
        }
        

    }
    
}

With a series of container heights arranged along an axis, how can you choose a pair such that the minimum of the two, times the distance between them, is the maximum for all possible pairs? Notice that if you start candidate pairs as the leftmost and rightmost, the only way to increase the possible area contained between them is if an inner height is greater than an outer height. The specific algorithm involves defaulting the best area to the area provided by the outermost pairs, then repeatedly choosing the smaller of the edge heights, and moving that pointer one step inwards before checking to see if the best area should update. If the pointers meet, best can no longer be improved.


// typeof h === "object" (array of numbers)

const maxArea = (h) => {

    let best = 0
    let left = 0
    let right = h.length - 1
    
    while(left < right){
        const candidate = Math.min(h[left], h[right], h[left]) * (right-left)
        if (candidate > best) best = candidate

        if(h[left] < h[right]) left++
        else right--
    }
    
    return best

}

Given two arrays of equal length, one that gives the amount of gas added to a car at a given index station, and the other that gives the cost to travel from that index’s station to the next index, is it possible to travel to all stations if they lie on a circular route, and if so, from what index should one start? A foundation for a linear solution lies in noticing that at every index the travel cost of gas can be subtracted from added gas to give the changeInGasAtIndex after one leaves it. If the sum of all changeInGasAtIndex is negative, it will not be possible to complete a loop because there is not enough total gas available. Otherwise, to determine what station index to leave from, you can loop the stations and keep a running total of gas in tank. Start a station marker at the 0 index. if the tank every goes negative, set the station marker to the next index. Return the station indicated at the end of the loop. Backtracking is not necessary because every sucessful movement from index to index must at a minimum involve 0 gas remaining.


// typeof gas === "object" (array of numbers)
// typeof cost === "object" (array of numbers)

const canCompleteCircuit = (gas, cost) => {
    
    let sum = 0
    let tank = 0
    let station = 0

    for (let i = 0; i < gas.length; i++) {
        const changeInGasAtIndex = gas[i] - cost[i]
        tank += changeInGasAtIndex
        sum += changeInGasAtIndex
        if(tank < 0) {
            tank = 0
            station = i + 1
        }
    }
    return sum > -1 ? station : -1
}

To determine the number of longest consecutive elements in an array, notice that you can start a search at every number where the prior number in the array does not exist. To set up for this, create a set with all numbers, then loop the array. Whenever the original number does not have an immediate prior, step through next numbers consecutively until next cannot be found, then update the best length as appropriate.


// typeof nums === "object" (array of numbers)

const longestConsecutive = (nums) => {

    let best = 0
    const numbers = new Set(nums)
    
    for (let number of nums){
        if (numbers.has(number-1)) continue
        let next = number + 1
        while (numbers.has(next)) next++
        best = Math.max(best, next-number)
    }

    return best

}

To rotate an array to the right by a certain number of steps, note that this means taking that number of elements from the right and adding them to the left of the array. You can do this in-place by determining how many elements must move. This value can be found by dividing the number of steps to right move the array by the length of the array and taking the remainder (modulo operation). Then simply splice off that number of elements from the back of the array and unshift them to the front.


// typeof nums === "object" (array of numbers)
// typeof k === "number"

const rotate = (nums, k) => {
    k %= nums.length
    nums.unshift(...nums.splice(-k))
}

To find the maximum length with equal 0 and 1 in an array that only contains 0 and 1, you can solve the problem linearly by counting extraOnes. Set that variable to 0, set a best to 0, and create a map that indicates there are 0 extraOnes at the -1 index. Then iterate the array. At every step, keep a running tally of extraOnes by incrementing if a 1 is found and decrementing if a 0 is found. Then, if that number has not yet been seen in the map, add it to the map with a value of the current index. Otherwise, update best if the index minus the first time that many extraOnes were found (use the map) is greater than best.


// typeof nums === "object" (array of numbers)

const findMaxLength = (nums) => {
    
    const map = new Map()
    map.set(0, -1)

    let best = 0
    let extraOnes = 0
    for (let i = 0; i < nums.length; i++){
        extraOnes += nums[i] ? 1 : -1

        if (!map.has(extraOnes)){
            map.set(extraOnes, i)
            continue
        }

        best = Math.max(best, i - map.get(extraOnes))
    }
    
    return best

}

Given an array of numbers, how can the number of subarrays that sum a given integer be found? (Subarrays are contiguous.) One linear method is to create a map to store the number of ways to sum to a given number already seen (starting it with a key of 0 pointing to a value of 1). Then iterate the array, keeping track of the sum of all numbers so far seen. At every step, if the sum counting the newly-seen number can be found in the map, the result can be increased by the number stored in the map. Then add a tally for the sum seen at the current location to the map.


// typeof nums === "object" (array of numbers)
// typeof k === "number"

const subarraySum = (nums, k) => {

  const map = new Map([[0,1]])

  let sum = 0
  let result = 0
  for (number of nums) {
    sum += number
    if (map.has(sum - k)) result += map.get(sum - k)
    map.set(sum, map.get(sum) + 1 || 1)
  }
  return result
  
}

Given an array containing arrays with paired starting and ending times for meetings, how many meeting rooms are needed? A key to understanding this question is realizing that the number of meeting roms needed is the maximum number of overlapping meetings at any time. To solve, you can first handle the edge case where there are no meetings and return 0. Else, split the input array into two sorted arrays, one that sorts the times of the starts from earliest to latest, and the other that sorts the times of the ends from earliest to latest. Set a startPointer and an endPointer both to 0, then loop as long as startPointer is less than the length of the input array. Each round, if the starts value at startPointer is less than the ends value at endPointer, this means a meeting is starting before the active one is ending. In this case, you can increase a result counter, starting at 0, by 1, and then increment the startPointer by 1 before the next round of the loop (since the meeting that just started has just been processed). In the other case, if the startPointer is pointing to a time greater or equal to the endPointer, this means there is no overlap between whatever meeting is starting and ending, and you can increment both pointers. The reason result ends at the correct value is because when result increments, the startPointer runs ahead by one step, meaning that overlaps of the same degree will not be double-counted, and only if the startPointer needs to run ahead even further will a new meeting room result be tallied.


// typeof intervals === "object" (array of arrays with 2 numbers)

const minMeetingRooms = (intervals) => {
  
  if (!intervals?.length) return 0

  const starts = intervals.sort((a,b) => a[0] - b[0]).map(a => a[0])
  const ends = intervals.sort((a,b) => a[1] - b[1]).map(a => a[1])
  let startPointer = 0
  let endPointer = 0

  let result = 0
  while (startPointer < intervals.length){
      if (starts[startPointer] < ends[endPointer]){
        result++
      } else {
        endPointer++
      }
      startPointer++
  }
  
  return result

}

To find three numbers that sum closest to a target number in an array, one solution involves first sorting the array from least to greatest. Then, you can set a best to Infinity, and create a loop that takes a firstPointer from the 0 index to two less than the final index. At every step, the secondPointer starts in each loop as firstPointer+1, while the thirdPointer starts at the last index of the array. At this space in the outer loop, while secondPointer is less than thirdPointer, find the sum of the values at the three indices. Return the sum directly if it matches the target, or update best if the absolute value of the target less the sum is a smaller value than the target less best. Otherwise, move the secondPointer forwards until it points to a new value or crosses the thirdPointer, and pull the thirdPointer back until it does the same. Then check again if secondPointer is less than thirdPointer, and if so, go back to the step where you find the sum again. If the outer loop finishes, return best.


// typeof nums === "object" (array of numbers)
// typeof target === "number"

const threeSumClosest = (nums, target) => {

  nums.sort((a,b) => a-b)
  let best = Infinity
  
  for (let firstPointer = 0; firstPointer < nums.length - 2; firstPointer++) {
    if (firstPointer > 0 && nums[firstPointer] === nums[firstPointer - 1]) continue

    let secondPointer = firstPointer + 1
    let thirdPointer = nums.length - 1
    while (secondPointer < thirdPointer) {
      const sum = nums[firstPointer] + nums[secondPointer] + nums[thirdPointer]
	  
      if (sum === target) return sum
      if (Math.abs(target - best) > Math.abs(target - sum)) best = sum
	  
      if (sum < target) {
        secondPointer++
        while(secondPointer < thirdPointer && nums[firstPointer] === nums[firstPointer - 1]) secondPointer++
      } else {
        thirdPointer--
        while (secondPointer < thirdPointer && nums[thirdPointer] === nums[thirdPointer + 1]) thirdPointer--
      }
    }
  }

  return best

}

Given intervals with starting and ending values, how to find the minimum count of intervals to remove so that the remaining do not overlap? First, you can sort the input array by the ending values. Next, set the previous interval to the first interval in the input array, and set a result counter to 0. Then, loop every other interval in the input array. If the ending value of previous is ever greater than the starting value of the interval being reviewed, increase result by 1, else update previous to be the interval being reviewed in the loop. This way, you are minimizing the ending value of previous.


// typeof intervals === "object" (array of arrays with 2 numbers)

const eraseOverlapIntervals = (intervals) => {

	intervals.sort((a,b) => a[1] - b[1])
	let previous = intervals[0]
	let result = 0

    for (let i = 1; i < intervals.length; i++){

			if (previous[1] > intervals[i][0]){
                result++
            } else {
                previous = intervals[i]
            }
    }

	return result

}

To find the maximum value of each sliding window of a given length in an array, you can start by declaring an empty result array, as well as an empty queue array, and a left pointer at 0. Then iterate through all values in the array, naming the moving index controlling the loop as the right pointer. At a given location, while the queue has a length and the last value indicated by queue is less than or equal to the value indicated by the right pointer, you, can pop that last value off the queue. Then, push the right index into the queue. These last two steps guarantee that the leftmost value in the queue is at a maximum. To manage the front end of the siding window, if the left index is greater than the index at the front of the queue, shift off the front element of the queue. Finally, if the right index has progressed far enough that the sliding window is at its appropriate length, push the value indicated by the index in the first position at the queue to result, and increase the left index by 1.


// typeof nums === "object" (array of numbers)

const maxSlidingWindow = (nums, k) => {
    const result = []
    const queue = []

    let left = 0
    for (let right = 0; right < nums.length; right++){
        while (queue.length && nums[queue[queue.length - 1]] <= nums[right]) {
            queue.pop()
        }
        queue.push(right)

        if (left > queue[0]) queue.shift()
        if (right + 1 >= k) {
            result.push(nums[queue[0]])
            left++
        }
    }

    return result

}

Given lists of employee start and end working times, how to consolidate all of these into a similar list of start and end times when all employees are off work? First all start and end times can be consolidated into a singleSchedule, which can then be sorted by start times. The first item in this can be highlighted as current, and then the singleSchedule can be reviewed. If the .end working time in current is less than the .start in the item being iterated, this means there is a gap between the .start and the .end. This is guaranteed to be real because for the .start of the iterated item to be greater than the .end of the current, all items with earlier .start values must have already been iterated. Therefore, push a new Interval with the .end of current and the .start of the iterated item to results, and update current to the iterated item. Otherwise, there is overlap between current and the iterated item, so update current’s .end to that of the iterated item if it is greater.


// typeof schedule === "object" (array of arrays of Interval classes containing .start and .end values that can be filled by a constructor)

const employeeFreeTime = (schedule) => {
    
    const result = []

    const singleSchedule = schedule.reduce((accumulator, value)=>{
        return [...accumulator, value]
    },[]).sort((a,b) => a.start - b.start)

    let current = singleSchedule[0]

    for (let interval of singleSchedule){
        if (current.end < interval.start){
            result.push(new Interval(current.end, interval.start))
        } else {
            current.end = Math.max(current.end, interval.end)
        }
    }

    return result
}

Stacks

To see if a string containing only (){}[] characters closes validly, create a dictionary where closing brackets point to opening brackets, and instantiate a stack. Then, iterate over the string, pushing opening brackets to the stack and popping the stack if a closing bracket is found which can close the stack’s top item. If a closing bracket is found that does not close the stack’s top item, or the stack is empty when a closing bracket is found, or the stack still has length when the string is fully iterated, return false – otherwise true should be returned.


// typeof s === "string"

const isValid = (s) => {

    const dictionary = {')':'(',']':'[','}':'{'}
    const stack = []

    for (let c of s){
        if(dictionary[c]){
            if(!stack.length ||
            stack[stack.length-1] !== dictionary[c]
            ){
                return false
            }
            stack.pop()
        } else {
            stack.push(c)
        }
    }

    return !stack.length

}

In JavaScript, a stack can be modeled by an array that only adds and removes values from one end, while a queue can be modeled by an array that only adds values on one end and removes them on the other end. To create push, pop, peek, and empty methods for a constructed queue, create a queue function and within, assign two arrays that will be treated as stacks under the this keywords. Implement the push method by adding a push method to the prototype of the queue function, which pushes to the first stack. Implement the empty method similarly by protoype, and by checking if both stacks are empty. The peek method, meanwhile, helps with pop – in peek, check if the second stack is empty. If so, pop from the first stack (where new values are added) and push to the second stack until the first stack is empty. This naturally brings the first value added to the first stack to the top of the second stack. The pop method can simply pop the top of the second stack after a peek.



const MyQueue = function() {

    this.stack1 = []
    this.stack2 = []

};

MyQueue.prototype.push = function(x) {

    this.stack1.push(x)

};

MyQueue.prototype.pop = function() {

    this.peek()
    return this.stack2.pop()

}

MyQueue.prototype.peek = function() {

    if(!this.stack2.length){
        while(this.stack1.length){
            const test = this.stack1.pop()
            this.stack2.push(test)
        }
    }
    return this.stack2[this.stack2.length-1]

}

MyQueue.prototype.empty = function() {

    return !this.stack1.length && !this.stack2.length

}

In two strings of lowercase letters and #, where # represents a backspace, are the strings equal once backspaces are considered? A solution involves parsing from the end of both strings, towards the front. At each step in the reverse traversal, a string that has not sucessfully reached its forward end checks for the presence of # at the location, and a backspace counter started at 0. If # is present, the backspace counter increments by 1, otherwise it decrements by 1, and in either case, the string pointer moves one step towards the front of the string. While the string has not reached its forward end, and the backspace counter is at least 1, or the string pointer is pointed at a #, the subloop continues. Once the subloop is completed for both strings, any value at the string indexes should match. If they do, both indices can decrement, otherwise the algorithm returns false. If the parent loop completes, return true.


// typeof s === "string"
// typeof t === "string"

const backspaceCompare = (s, t) => {

    let sIndex = s.length-1
    let tIndex = t.length-1

    while(sIndex > -1 || tIndex > -1){
        sIndex = backwards(s, sIndex)
        tIndex = backwards(t, tIndex)
        
        if(s[sIndex] !== t[tIndex]){
            return false
        } else {
            sIndex--
            tIndex--
        }
    }

    return true

};

const backwards = (string, index) => {

    let backspace = 0
    while(index > -1 && (string[index] === '#' || backspace > 0)){
        backspace += (string[index] === '#' ? 1 : -1)
        index--
    }
    return index

}

}

Reverse Polish Notation involves pushing numerical values to a stack, and otherwise running a detected operator on the two values found behind it in the stack. When values are used they can be popped, and the new value after operation is pushed to the stack as if it was in the original token series (this is a way the notation represents parentheticals).


// typeof tokens === "object" (array of strings)

const evalRPN = (tokens) => {

    const stack = []

    for (let token of tokens){
        if(!'+-*/'.includes(token)) stack.push(Number(token))
        else {
            const b = stack.pop()
            const a = stack.pop()

            if(token === '+'){
                stack.push(a + b)
            } else if (token === '-'){
                stack.push(a - b)
            } else if (token === '*'){
                stack.push(a*b)
            } else {
                const test = a/b
                if(test < 0) stack.push(Math.ceil(test))
                else stack.push(Math.floor(test))
            }
        }
    }

    return stack[0]

}

To create a stack that can push, pop, find the top value, and find the minimum value, all in constant time, you can seed two empty arrays in the starter function, values and minimums. To push values, you can always push a new value to the values array, but only push to the minimums array if that array is empty or the value is less than or equal to the previous minimum. To pop, you can always pop from the values array but only pop from the minimums array if the last value there matches the value popped from values. Top just checks the end of the values array, while getting the minimum just checks the end of the minimums array.


// typeof val === "number"

const MinStack = function() {
    this.values = []
    this.minimums = []
}

MinStack.prototype.push = function(val) {
    if(!this.minimums.length || val <= this.minimums[this.minimums.length-1]){
        this.minimums.push(val)
    }
    this.values.push(val)
}

MinStack.prototype.pop = function() {
    const test = this.values.pop()
    if(test === this.minimums[this.minimums.length-1]){
        this.minimums.pop()
    }
    return test
}

MinStack.prototype.top = function() {
    return this.values[this.values.length-1]
}

MinStack.prototype.getMin = function() {
    return this.minimums[this.minimums.length-1]
}

To create an array that gives the distance between a given number in an input array and the next greater number, which can be understood as number of days until a greater temperature, you can create a stack and loop the input array. While the stack has length and the current value in the input array is greater than the value indicated at end of the stack, you can pop the index of the dayWithTemperatureJustDefeated from the end of the stack, assigning to that index in a result array the positive difference between the currrent loop index and the index of the dayWithTemperatureJustDefeated. Then you can push the current loop index to the stack. After the traversal of the input array, any indicies that could not be popped should be used to assign a value to the result array that indicates no later greater value could be found, which in the LeetCode version of the question is 0.


// typeof temperatures === "object" (array of numbers)

const dailyTemperatures = (temperatures) => {

    const stack = []
    const result = []

    for (let i = 0; i < temperatures.length; i++) {
        while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]){
            const dayWithTemperatureJustDefeated = stack.pop()
            result[dayWithTemperatureJustDefeated] = i - dayWithTemperatureJustDefeated 
        }
        stack.push(i)
    }

    for (let index of stack){
        result[index] = 0
    }

    return result

}

If you have an encoded string where numbers followed by brackets multiply the contents of the brackets, how to return the decoded string? One way to solve this involves a repeatStack and a stringStack. Both are arrays, and repeatStack starts empty, while stringStack starts with an empty string. A latestRepeats is also declared as an empty string. As the encoded string is looped, the stacks are used to deode. Numbers are added as string characters to the latestRepeats variable. The character [ requires the latestRepeats to be turned in to a number type and pushed to the repeatStack, while latestRepeats is reset to an empty string, and the stringStack also take an empty string. The character ] requires both repeatStack and stringStack to pop, and the contents of the popped value from stringStack be added to new last value of stringStack a number of times equal to the value popped from repeatStack. For any other character processed from the enoded string, add it to the last element of stringStack. After the encoded string is fully processed, return what should be the only value in stringStack.


// typeof s === "string"

const decodeString = (s) => {

    const repeatStack = []
    const stringStack = [""]
    let latestRepeats = ""

    for (let character of s){
        if (character === "["){
            repeatStack.push(Number(latestRepeats))
            latestRepeats = ""
            stringStack.push("")
        } else if (character === "]"){
            const string = stringStack.pop()
            const repeat = repeatStack.pop()
            stringStack[stringStack.length-1] += string.repeat(repeat)
        } else if (Number.isInteger(Number(character))) {
            latestRepeats += character
        } else {
            stringStack[stringStack.length-1] += character
        }
    }
    return stringStack[0]

}

An array of integers where positive numbers head right, negative numbers head left, and 0 does not exist can be imagined as a field of asteroids. What is the state of the array after collisions, when in collisions smaller numbers are removed, and in cases of equal numbers both are removed? This problem can be solved with a stack. After the stack is created, loop the input array. Push every positive element to the array. In cases of negative elements, as long as there is a positive most recent (rightmost) item on the stack, pop stack elements until the absolute value of the negative element is smaller or equal to the most recent item on the stack. If the ‘asteroid’ moving left is smaller, continue to the next step in the outermost loop, and if the ‘asteroids’ are equal, pop the most recent item on the stack before continuing to the next step in the outermost loop. Otherwise, the asteroid moving to the left has cleared the rightwards ‘asteroids’ on the stack, and should be pushed to it.


// typeof asteroids === "object" (array of numbers)

const asteroidCollision = (asteroids) => {

    const stack = []

    for (let asteroid of asteroids){
        if (asteroid > 0){
            stack.push(asteroid)
            continue
        }

        const absAsteroid = Math.abs(asteroid)
        let addAsteroid = true
        while(stack.length && stack[stack.length-1] > 0){
            if(stack[stack.length-1] > absAsteroid){
                addAsteroid = false
                break
            } else if (stack[stack.length-1] === absAsteroid){
                addAsteroid = false
                stack.pop()
                break
            }

            stack.pop()
        }

        if (addAsteroid) stack.push(asteroid)

    }

    return stack

}

To build a string calculator of non-negative integers that handles blank spaces, +,-,*, and /, while truncating any / operations towards 0, you can note that two operators will act immediately – * and /. Meanwhile, + and - may not act immediately depending on whether they are surrounded by higher-priority operators. In order to calculate, an empty stack can be created, as well as a value array to collect an active number. A sign variable can also be started at +, and the input string can be iterated. In the iteration, empty spaces can be skipped, and numbers can be pushed to the value array. If a sign is detected at the location in the string, this means it is time for the previous sign to be processed. If the saved sign is +, push the number form of the joined value to the stack, and if the saved sign is -, push the negative of the numerified joined form of the value to the stack. If the saved sign is *, this means the top item in the stack can also be processed. Pop it, multiply by the numerified joined form of the value, and push the result back to the stack. The process for division is similar – the difference is to divide and truncate towards 0 before pushing back to the stack. Finally at the location, if a sign was detected, update the sign variable to store the sign at the current location, and replace value with an empty array. At the end of the string, evaluate the last sign and value per the above rules, then sum all the entries in stack and return this. (There may be multiple entries left at the end in the stack because + and - signs do not consolidate items in the stack.)


// typeof s === "string"

const calculate = (s) => {

  const stack = []
  let value = []
  let sign = "+"
  for(let i = 0; i <= s.length; i++){    
    const viewing = s[i]

    if(viewing === " "){
        continue
    }

    if(!isNaN(viewing)){
        value.push(viewing)
        continue
    }
    const valueAsNumber = Number(value.join(""))

    if (sign === "+"){
        stack.push(valueAsNumber)
    } else if (sign === "-"){
        stack.push(-valueAsNumber)
    } else if (sign === "*"){
        stack.push(stack.pop()*valueAsNumber)
    } else if (sign ==="/"){
        const untruncatedDivision = stack.pop()/valueAsNumber
        const step = untruncatedDivision > 0 ? Math.floor(untruncatedDivision) : Math.ceil(untruncatedDivision)
        stack.push(step)
    }

      sign = viewing
      value = []
  }
  return stack.reduce((a,b)=> a + b)

}

Given an array of heights that can be thought to exist at the indicies given in the array, what is the volume of “rain water” falling straight down that can fill this structure without spilling? To solve this problem, you can first compute a peakIndex of any maximum value in the array. The amount of “water” added in any index location can never be greater than the value at the peakIndex. Next, start a result counter at 0, and a leftHeight at 0, and move through the input array until you reach the peakIndex. At every location, if the height at the location is less than leftHeight, add the positive difference to result. Else, update the leftHeight to the value at the current location. Once peakIndex is reached, repeat this process from the other end of the array with a rightHeight.


// typeof height === "object" (array of numbers)

const trap = (height) => {

    const peakIndex = height.indexOf(Math.max(...height))
    let result = 0

    let leftHeight = 0
    for(let i = 0; i < peakIndex; i++){
        if(height[i] < leftHeight) result += leftHeight - height[i]
        else leftHeight = height[i]
    }

    let rightHeight = 0
    for(let i = height.length-1; i > peakIndex; i--){
        if(height[i] < rightHeight) result += rightHeight - height[i]
        else rightHeight = height[i]   
    }

    return result

}

To create a calculator from a string that handles +, -, and parentheses, you can use recursion. On the outermost call of the main function, create an endParenthesisLookupFromStart that gives keys for open parentheses indices, each paired with the value of the linked close parentheis index. This and the input string are passed to every layer, along with start and end indices that begin in the outermost call as the first and last indices of characters in the input string. Within the body of the main function, create an empty nums array, and a sign variable intially set to 1. Then move along characters in the input string from the given start index to the end. Skip any place spaces. If a ( is found, run the parent function recursively on the part of the string within that parenthesis and its pair, then push the sectionResult times the sign to nums. If a + is found, update the sign to the number 1, and if a - is found, update the sign to the number -1. If the character being iterated in the loop itself is a number, collect all numbers immediately to the left to get the full number, and then push this sectionResult to nums. Make sure to update the iterating index. When the loop is done, return the sum of all values in nums.


// typeof s === "string"

const calculate = (s, start = 0, end = s.length - 1, endParenthesisLookupFromStart = findParentheses(s)) => {
  const nums = []
  let sign = 1
  for (let i = start; i <= end; i++) {
    if (s[i] === " ") continue

    if (s[i] === "(") {
      const sectionResult = calculate(s, i + 1, endParenthesisLookupFromStart[i] - 1, endParenthesisLookupFromStart)
      nums.push(sign * sectionResult)
      i = endParenthesisLookupFromStart[i]
    } else if (s[i] === "+") {
      sign = 1;
    } else if (s[i] === "-") {
      sign = -1;
    } else if (!isNaN(s[i])) {
      const str = parseNum(s, i)
      const sectionResult = Number(str)
      nums.push(sign * sectionResult)
      i += str.length - 1
    }
  }
  return nums.reduce((a, b) => a + b)
}

const findParentheses = (s) => {
  const endParenthesisLookupFromStart = {}
  const stack = []
  for (let i = 0; i < s.length; i++) {
    if (s[i] === "(") {
      stack.push(i)
    } else if (s[i] === ')') {
      const left = stack.pop()
      endParenthesisLookupFromStart[left] = i
    }
  }
  return endParenthesisLookupFromStart
}

const parseNum = (s, start) => {
  let i = start;
  let numberAsString = ""
  while (!isNaN(s[i])) {
    numberAsString += s[i]
    i += 1
  }
  return numberAsString
}

To find the largest rectangle in a histogram, with the x-coordinate represented by the index of an input array and the height represented by the value at that index, you can solve the problem with a stack. Loop the input array, and at every step consider if the stack has a length and, if so, if the the value in the pair on top of the stack representing the height at some earlier location is greater than the height at the current location. Meeting this condition means that the maximum height of any rectangle starting at the current location and looking backwards is clamped to the height at the current location, which represents a ceiling. As long as a height stored on top of the stack is greater than a height at the current index, pop the top of the stack, which should contain a height and a location from where that height begins. Every time a pop happens, update a result starting at 0 if the height in the popped value times the difference between the index and the popped location is larger than result. Then update a heightStartingLocation variable started at the original index to the index of the popped value. Once the popping is done (or never started), push to the stack a pair of numbers – the height at the current index, and the heightStartingLocation. This indicates where you can pretend the height began from, as all taller heights were popped off the stack (and can contribute to a rectangle that is at least as tall as the present height). Finally, after the input array is iterated, pop stack items one by one and update the result if any height times the difference between the number of heights and the location just popped is ever greater than result.


// typeof heights === "object" (array of numbers)

const largestRectangleArea = (heights) => {

    const stack = []
    let result = 0
    
    for(let i = 0; i < heights.length; i++){
        let heightStartingLocation = i
        while(stack.length && stack[stack.length-1][0] > heights[i]){
            const [height, location] = stack.pop()
            const candidateArea = height*(i-location)
            if (candidateArea > result) result = candidateArea
            heightStartingLocation = location
        }
        stack.push([heights[i], heightStartingLocation])
    }
    while(stack.length){
        const [height, location] = stack.pop()
        const candidateArea = height*(heights.length-location)
        if (candidateArea > result) result = candidateArea
    }
    return result

}

To create a data structure that lets you push elements and pop the most frequent element, with ties being broken by most recent element added, you can set a function to a variable and set an empty array under this, elementsByFrequency, as well as an empty object under this, elementCounts. For the push method under the prototype, increment the input in elementCounts by 1, or create an entry as 1. If the count is greater than the length of elementsByFrequency, indicating a count not seen, push an array to elementsByFrequency containing the only the input. Else, push the input to the location in elementsByFrequency equal to the count minus 1 (so that the 0 index of elementsByFrequency stores counts of 1). For the pop method under the prototype, identify the mostCommonElements of elementsByFrequency (the rightmost array). Pop a value from this array to return, but before you do, pop elementsByFrequency if the mostCommonElements location is now empty. Also decrease the count of the element popped by 1 in elementCounts.



const FreqStack = function() {
    this.elementsByFrequency = []
    this.elementCounts = {}
}

FreqStack.prototype.push = function(x) {

    this.elementCounts[x] = (this.elementCounts[x] || 0) + 1

    if (this.elementsByFrequency.length < this.elementCounts[x]){
        this.elementsByFrequency.push([x])
    } else {
        this.elementsByFrequency[this.elementCounts[x]-1].push(x)
    }
}

FreqStack.prototype.pop = function() {
    const mostCommonElements = this.elementsByFrequency[this.elementsByFrequency.length - 1]
    const result = mostCommonElements.pop()
    if (!mostCommonElements.length) {
        this.elementsByFrequency.pop()
    }
    this.elementCounts[result]--
    return result
}

To find the longest set of valid parentheses in a string containing only parentheses, you can iterate the input string, and push a 0 to a stack starting with a 0 if an ( is seen. Otherwise, if the stack length is greater than 1, pop the back item off the stack and add the value of the pop plus 2 to the value of the item before that, then update a result starting at 0 to be the best of itself or the value of the item just updated. If the stack length is only 1 and you see a ), indicating there is no valid ( in the stack to connect to the ) you are visiting, set the only location in the stack to 0 to indicate that a parentheses set ending at this point is misformed and cannot contribute to the maximum length of a parentheses set starting later.


// typeof s === "string"

const longestValidParentheses = (s) => {
    
	let result = 0
	const stack = [0]
    for (let character of s){
        if (character === '(') {
			stack.push(0)
            continue
		}

        if (stack.length > 1) {
            stack[stack.length - 2] += stack.pop() + 2
            result = Math.max(result, stack[stack.length - 1])
            continue
        }

        stack[0] = 0
    }

	return result

}

Linked Lists

LeetCode implements a singly-linked list like this:



function ListNode(val, next) {

    this.val = (val===undefined ? 0 : val)
    this.next = (next===undefined ? null : next)

}

…or sometimes this:



function ListNode(val) {

    this.val = val
    this.next = null

}

To merge two linked lists, declare an empty link node, and create an extra pointer to this node. While neither list is empty, select the minimum value from the heads of either list, append this to the pointer, then redefine both the pointer and the head of the list with that minimum value their respective .next values. Once one list is empty, append whichever list was not emptied to the pointer, and use link.next as the return value.


// typeof list1 === "object" (linked list)
// typeof list2 === "object" (linked list)

const mergeTwoLists = (list1, list2) => {

    const link = new ListNode()
    let pointer = link

    while(list1 && list2){
        if(list1.val < list2.val){
            pointer.next = list1
            list1 = list1.next
        } else {
            pointer.next = list2
            list2 = list2.next
        }
        pointer = pointer.next
    }

    if(list1){
        pointer.next = list1
    } else {
        pointer.next = list2
    }

    return link.next

}

Cycles in linked lists can be detected by setting a slow pointer to the .next of the head, if available, and setting a fast pointer to the .next.next of the head. At each step in a while loop attempt to advance the slow pointer by 1 step and the fast pointer by 2. Then check for equality of the pointers. If the pointers point to exactly the same reference, they will return true, which signals a cycle, and the fast pointer overtaking the slow. If the fast pointer reaches an empty reference before this happens, don’t be in the loop and return false.


// typeof head === "object" (linked list)

const hasCycle = (head) => {

    let slow = head?.next
    let fast = head?.next?.next

    while(fast){
        if (slow === fast) return true
        slow = slow?.next
        fast = fast?.next?.next
    }

    return false

}

To reverse a linked list, create a null link, then perform the following steps while the original linked list still has a filled head node:

1) Save the `head` of the original linked list as `next`.
2) Assign `link` to `head.next` (`link` builds the leading part of the original linked list in reversed order).
3) Assign `head` to `link` (head now points through `.next` to the original `link`).
4) Assign the saved `next` to `head`.

// typeof head === "object" (linked list)

const reverseList = (head) => {

    let link = null

    while(head){
        const next = head.next
        head.next = link
        link = head
        head = next
    }

    return link

}

To find the middle node in a linked list (second of the two if the number of nodes is even), create fast and slow pointers and start them at head. While there is a fast.next, assign slow to the next node and next two nodes forward along the list. Once this loop resolves slow should point to the middle node by the problem definition.


// typeof head === "object" (linked list)

const middleNode = (head) => {
    
   let slow = head
   let fast = head

   while(fast?.next){
         slow = slow.next
         fast = fast.next?.next
   }

   return slow

}

To find if a linked list contains a palindrome of values, it is necessary to compare values from either end of the list, stepping towards the middle. The head of the list is readily available, but to get to the other end effectively, a traversal is needed. Perform a traversal similar to Middle of the Linked List , just above, which should put a slow pointer at approximately the middle node. The suffix can the be reversed in a pattern similar to Reverse Linked List . Finally, both the original head and the suffix can be stepped through together to see if each pair of numbers matches. Make sure your implementation accounts for the possibility of an odd number of nodes and does not check the middle node against anything.


// typeof head === "object" (linked list)

const isPalindrome = (head) => {  
  if (!head.next) return true
  
  let slow = head
  let fast = head
  
  while (fast) {
    slow = slow.next
    fast = fast.next?.next
  }
  
  slow = reverse(slow)
  
  while (slow) {
    if (slow.val !== head.val) return false
    slow = slow.next
    head = head.next
  }
  
  return true
};

const reverse = (head) => {
  let link = null
  
  while (head) {
    const next = head.next
    head.next = link
    link = head
    head = next
  }
  
  return link
}

To create a cache that evicts the least recent key when a new key is added that goes over a cache capacity, we can take advantage of the properties of the JavaScript Map object. One strategy involves declaring a function and assigning it to a variable. When this function is instanced, for example, with new LRUCache(4), assign the argument capacity to this (capacity is 4 in the example) and also assign a new Map under this. For get, if there is no key, you can return -1, else get, delete, and reset the key, and return the value. This makes it so that Map’s internal ordering notes this key as most recent. The set method is similar – simply delete the key if it already exists, set it again, and then use JavaScript map functions to delete the first key added to the Map. Note that this and many other solutions currently (December 2022) often time out on LeetCode but do periodically pass. The primary difference between this and Cjamm’s solution is that this code will always attempt to delete keys in either method.


// typeof capacity === "number"

const LRUCache = function(capacity) {
    this.capacity = capacity
    this.map = new Map()
}

LRUCache.prototype.get = function(key) {
    if(!this.map.has(key)) return -1
    const value = this.map.get(key)
    this.map.delete(key)
    this.map.set(key, value)
    return value
}

LRUCache.prototype.put = function(key, value) {
    this.map.delete(key)
    this.map.set(key, value)
    if (this.map.size > this.capacity) this.map.delete(this.map.keys().next().value)
}

To remove the nth node from the end of a list, you can count the length of the list by iterating, then subtract from length the nth item to be removed to find the number of the item to be removed starting from the front. Finally, with a new pointer from the front of the list, iterate until one step before the item to be removed, and from that item, replace the .next with .next.next if available, otherwise null. If the item to be removed is at the head of the list, simply return the .next from the head.


// typeof head === "object" (linked list)
// typeof n === "number"

const removeNthFromEnd = (head, n) => {

  let counterCopy = head

  let length = 0
  while (counterCopy) {
    length++
    counterCopy = counterCopy.next
  }
  
  let countToRemove = length - n
  if (!countToRemove) return head.next

  let removerCopy = head
  while (countToRemove > 1) {
    countToRemove--
    removerCopy = removerCopy.next
  }

  removerCopy.next = removerCopy.next?.next || null
  return head

}

How to swap every two adjacent nodes in a linked list? You can run a function recursively – if there is no .next simply return head, otherwise save the head as nodeToBeMovedBack, save the .next.next as the recursiveCallArgument, move the head to head.next, and take the nodeToBeMovedBack as the new head.next. Finally, assign the recursive call to head.next.next.


// typeof head === "object" (linked list)

const swapPairs = (head) => {

    if(head?.next){
        const nodeToBeMovedBack = head
        const recursiveCallArgument = head.next.next
        head = head.next
        head.next = nodeToBeMovedBack
        head.next.next = swapPairs(recursiveCallArgument)
    }

    return head

}

To change a linked list so that all odd-indexed elements are followed by all even-indexed elements, if the linked list has a length, you can set three pointers. First, an odd starting at the head, second, an even starting at head.next, and third, an extra pointer staying at headEven. While there is an even.next (aka what should be the next odd), set the .next of both odd and even to their .next.next and then set odd and even themselves to their .next. Make sure to do odd first so the links for even are not broken. Once there is no more even.next, set odd.next to headEven and retun this.


// typeof head === "object" (linked list)

const oddEvenList = (head) => {

    if (head === null) return head
    
    let odd = head
    let even = head.next
    const headEven = even

    while (even?.next) {
        odd.next = odd.next.next
        odd = odd.next
        even.next = even.next.next
        even = even.next
    }
    odd.next = headEven

    return head

}

If two linked lists represent numbers in reversed order, how to add them into a new linked list? You can create a link pointer to help hold the eventual head, a copy joined pointer to iterate through the digits of the result, a pointer1 and pointer2 to point to the first node in each input list, and a carry variable starting at 0 to handle overflow. While pointer1 or pointer2 are not null, add the sum of the values at both, if any, plus carry, to a sum. The tens place of this value becomes carry for the next step, and the ones place is added as the value to the next joined node. Then pointer1 and pointer2 move to their .next, if available. At the end of the while loop, if there is still a value in carry, add it as one final node, and then return the .next of the original link.


// typeof l1 === "object" (linked list)
// typeof l2 === "object" (linked list)

const addTwoNumbers = (l1, l2) => {

    const link = new ListNode()
    let joined = link

    let carry  = 0
    let pointer1 = l1
    let pointer2 = l2

    while(pointer1 || pointer2) {
        const sum = carry + (pointer1?.val || 0) + (pointer2?.val || 0)
        carry = Math.floor(sum / 10)
        joined.next = new ListNode(sum % 10)
        joined = joined.next
        pointer1 = pointer1?.next || null
        pointer2 = pointer2?.next || null
    }

    joined.next = carry ? new ListNode(carry) : null

    return link.next

}

How to return a linked list after sorting its nodes in ascending order? Recursion is a solution. The base case, where the input does not have a .next, involves returning the input. Else you can use fast and slow pointers to find a middle pointer with slow. Set a breakpoint at slow and assign its .next as the second half of the series, then make breakpoint’s own .next null, which separates out the first half. Make a recursive call for both halves to ensure they are sorted, and then compare the heads of each repeatedly, adding whichever is lower but present to a new series of nodes building off an empty link. When both heads are exhausted, return link.next.


// typeof head === "object" (linked list)

const sortList = (head) => {
    if (!head?.next) return head
    
    let fast = head
    let slow = head
    while (fast.next?.next) {
        fast = fast.next.next
        slow = slow.next
    }
    
    const first = head
    const breakpoint = slow
    const second = slow.next
    breakpoint.next = null

    let sortedFirst = sortList(first)
    let sortedSecond = sortList(second)
    
    const link = new ListNode()
    let pointer = link
    while (sortedFirst || sortedSecond) {
        if (!sortedSecond || sortedFirst?.val < sortedSecond?.val) {
            pointer.next = sortedFirst
            sortedFirst = sortedFirst.next
        } else {
            pointer.next = sortedSecond
            sortedSecond = sortedSecond.next
        }
        pointer = pointer.next
    }
    pointer.next = null
    
    return link.next

}

To reorder a linked list in-place such that the second value would become the old last value, and the fourth value would become the old second-to-last value, you can find the endOfFrontPart value with fast and slow pointers, then assign its .next as the leading part of the back half, the first inorderBackNode. Then you can make endOfFrontPart’s .next null to pinch off the front of the linked list from the back. The linked list starting with inorderBackNode can then be reversed. In the final part of the algorithm, create an empty link node and a copy of the original linked list head as forward. While reversed exists, assign the .next of link to forward, advance both link and forward to their .next, then assign the .next of link to reversed and advance both link and reversed to their .next. Finally, to account for a possible odd number of nodes, assign link.next to forward.


// typeof head === "object" (linked list)

const reorderList = (head) => {

    let fast = head
    let slow = head
    while(fast?.next){
        fast = fast.next.next
        slow = slow.next
    }

    let endOfFrontPart = slow
    let inorderBackNode = endOfFrontPart.next
    endOfFrontPart.next = null

    let reversed = null
    while (inorderBackNode){
        const next = inorderBackNode.next
        inorderBackNode.next = reversed
        reversed = inorderBackNode
        inorderBackNode = next
    }

    let link = new ListNode()
    let forward = head
    while (reversed){
            link.next = forward
            forward = forward.next
            link = link.next

            link.next = reversed
            reversed = reversed.next
            link = link.next
    }

    link.next = forward

}

To rotate a linked list to the right by a certain number of places, you can note that an empty list returns itself. Otherwise, a next step is to count the length of the list. Update the rotation count to its remainder after dividing by the length. If the new rotation count is 0 or the length, you can return the original linked list. Otherwise, you can save the last node, and with the new rotation count, iterate a pointer from the beginning of the list until you are the rotation count of spaces away from the last node. Save this location as the result, set the previous node’s .next to null, and set the last’s .next to the original head. Then return result.


// typeof head === "object" (linked list)
// typeof k === "number"

const rotateRight = (head, k) => {
  if (!head) return head
  let length = 1
  let pointer = head
  while (pointer.next) {
    length++
    pointer = pointer.next
  }

  k %= length
  if (!k || k === length) return head

  const last = pointer
  let pointer2 = head

  for (let i = 0; i < length-k-1; i++){
      pointer2 = pointer2.next
  }

  const result = pointer2.next
  pointer2.next = null
  last.next = head

  return result

}

To reverse nodes in a link list k at a time, you can implement the change in batches with recursive calls of a parent function. In the parent function, if there is no head node, you can return null immediately. Else, you can create a new pointer set to the head for the first kSegmentTraversal. Make this traversal, and if you cannot get all the way through return head immediately because you cannot reverse a full segment. Otherwise, assign a .next to a next variable such that the number of nodes before next is k. Then replace the .next with null such that the next variable contains a list that is “pinched off.” Reverse the head of the function as normal, and now that the head is the last node, assign its .next to the main function run on the next variable saved earlier, with the original k. Then return the kSegmentTraversal because the node at the end of that has become the new head.


// typeof head === "object" (linked list)
// typeof k === "number"

const reverseKGroup = (head, k) => {

  if (!head) return null
  let kSegmentTraversal = head
  for (let i = 1; i < k; i++) {
    kSegmentTraversal = kSegmentTraversal.next
    if (!kSegmentTraversal) return head
  }
  const next = kSegmentTraversal.next
  kSegmentTraversal.next = null
  reverse(head)
  head.next = reverseKGroup(next, k)
  return kSegmentTraversal
}

const reverse = (head) => {
  let link = null
  
  while (head) {
    const next = head.next
    head.next = link
    link = head
    head = next
  }
  
  return link
}

Strings

To detect if a string is a palindrome, sanitize the string as appropriate (for example, removing spaces and standardizing capitalization), then initialize string start and end pointers. While start is at a lower index than end check to see if the values at each location match, then move each pointer one index closer to the center. Any mismatch allows the function to immediately return false while otherwise true should be the return.


// typeof s === "string"

const isPalindrome = (s) => {

    let start = 0
    s = s.replace(/[^0-9a-zA-Z]/g,'').toLowerCase()
    let end = s.length - 1

    while(start < end){
        if(s[start] !== s[end]) return false
        start++
        end--
    }

    return true

}

Two strings are valid anagrams of each other if they have exactly the same letter count. A test involves creating an array with zeroed indices for every letter. Loop over one string, incrementing at the appropriate array index as letters are read. Then loop over the second index decrementing at the appropriate array index as letters are read. If the strings are anagrams, the resulting array should be filled with zeroes.


// typeof s === "string"
// typeof t === "string"

const isAnagram = (s, t) => {

    const arr = new Array(26).fill(0)

    for (let i = 0; i < s.length; i++){
        arr[s.charCodeAt(i)-97]++
    }

    for (let i = 0; i < t.length; i++){
        arr[t.charCodeAt(i)-97]--
    }

    return arr.every(a=>!a)

}

The longest palindrome from a case-sensitive string of letters is equivalent to the number of matching pairs plus one, or the length of the string, whichever is shorter. In other versions of the problem, the string may need to be filtered or transformed.


// typeof s === "string"

const longestPalindrome = (s) => {

    const upper = new Array(26).fill(0)
    const lower = new Array(26).fill(0)

    for(let i = 0; i < s.length; i++){
        if(s.charCodeAt(i) < 97){
            upper[s.charCodeAt(i)-65]++
        } else {
            lower[s.charCodeAt(i)-97]++
        }
    }

    let pairs = 0
    for (let u of upper){
        pairs += Math.floor(u/2)
    }
    for (let l of lower){
        pairs += Math.floor(l/2)
    }

    return Math.min(pairs*2+1, s.length)

}

To find the longest common prefix in an array of strings, any string can be chosen as the comparator, since the longest common prefix cannot be longer than any arbitrarily chosen string. Cycle through character locations from left to right for every string in the array, starting with the leftmost character in each string. Every time the comparator at a given index matches the character in every other string at that same index, add that character to the prefix result. As soon as there is a mismatch, or once the double loop ends with no mismatches, return prefix.


// typeof strs === "array of strings"

const longestCommonPrefix = (strs) => {

  let prefix = []

  for (let i = 0; i < strs[0].length; i++) {
    for (let j = 1; j < strs.length; j++) {
      if (strs[0][i] !== strs[j][i]) return prefix.join("")
    }

    prefix.push(strs[0][i])
  }

  return prefix.join("")

}

To find the longest substring without repeating characters, you can use two pointers, a leading pointer and a backPointer, both of which can start at 0. Advance the leading pointer as in a loop, and at each location in the string add the character code of the character to an object of all character codes in the visited string. Then, while a duplicate character code is detected, subtract the character pointed to by the backPointer from the table, and increment the back pointer. Once the substring between the leading pointer and backPointer has no repeated characters, use its length to replace a best string length as needed.


//typeof s === "string"

const lengthOfLongestSubstring = (s) => {

    const charCount = new Array(127).fill(0)

    let best = 0
    let backPointer = 0

    for(let i = 0; i < s.length; i++){
        charCount[s.charCodeAt(i)]++

        while(charCount[s.charCodeAt(i)] === 2){
            charCount[s.charCodeAt(backPointer)]--
            backPointer++
        }

        best = Math.max(best,i+1-backPointer)
    }

    return best

}

To convert a stringified number to a 32-bit signed integer, you can remove whitespace, parse the number with base 10, return 0 if the result is not a number, and clamp the value to the available space if the number is too great or too small.


// typeof str === "string"

const myAtoi = (str) => {
    
    const integer = parseInt(str.trim(), 10)

    if (isNaN(integer)) {
        return 0
    } else if (integer >= Math.pow(2, 31)) {
        return Math.pow(2, 31) - 1   
    } else if (integer < Math.pow(-2, 31)) {
        return Math.pow(-2, 31)
    } else {
        return integer
    }
}

To find the longest palindromic substring of a string, you can simply iterate the string, and for every location make new pointer pairs expand outwards in both directions, continuing if each pointer pair matches and updating the result string if appropriate. Each location on the string being looped needs two expansions, one that does not test the active index, using the index just before and just after as the first pair (checking odd length strings), and one that uses the active index with the next index as the first pair of the location, and expands outwards from there (checking even length strings). If any index is out of bounds, loops should end as needed.


// typeof s === "string"

const longestPalindrome = (s) => {

    let result = s[0]

    for (let i = 0; i < s.length; i++){

        let left = i - 1
        let right = i + 1
        while(left >= 0 && right < s.length && s[left] === s[right]){
            if(right-left+1 > result.length) result = s.slice(left, right+1)
            left--
            right++
        }

        left = i
        right = i + 1
        while(left >= 0 && right < s.length && s[left] === s[right]){
            if(right-left+1 > result.length) result = s.slice(left, right+1)
            left--
            right++
        }
    }

    return result

}

How to return all start indicies of an anagram of one string in another string? First, if the parent string is shorter than the string that may appear inside as an anagram, you can immediately return an empty array. Then you can create a countMap of the string to be made into an anagram. Separately, create a count of the number of unique letters in the potential anagram. The final two variables needed are a result array and a count of numberOfAnagramLettersFullySeen. The remainder of the algorithm is a linear pass through the larger parent string. At every new location, if the letter being pointed to is in the countMap, decrease the value at the appropriate letter in the countMap by 1. If this newCount is 0, that means you can increment numberOfAnagramLettersFullySeen by 1. In the same step in the loop through the parent string, check the location further left by the length of the anagram string, if available – this location should no longer be part of the examined string. Increase countMap and decrease numberOfAnagramLettersFullySeen as needed based on what might have been in the location no longer being considered. As the third and final step for each location in the loop, push the index less the length of the anagram string plus 1 to the results array if the numberOfAnagramLettersFullySeen is the same as the number of unique letters. We can only ever consider strings of the required length.


// typeof s === "string"
// typeof p === "string"

const findAnagrams = (s, p) => {

    if (s.length < p.length) return []

    const countMap = new Map()
    let letters = 0
    for (let character of p){
        if (!countMap.has(character)){
            letters++
            countMap.set(character, 0)
        } 
        countMap.set(character, countMap.get(character) + 1)
    }

    const results = []
    let numberOfAnagramLettersFullySeen = 0
    
    for (let i = 0; i < s.length; i++){
        if (countMap.has(s[i])){
            const newCount = countMap.get(s[i]) - 1
            countMap.set(s[i], newCount)
            if (newCount === 0) numberOfAnagramLettersFullySeen++
        }
        if (i >= p.length && countMap.has(s[i-p.length])){
            const newCount = countMap.get(s[i-p.length]) + 1
            countMap.set(s[i-p.length], newCount)
            if (newCount === 1) numberOfAnagramLettersFullySeen--
        }
        if (numberOfAnagramLettersFullySeen === letters) results.push(i-p.length+1)
    }
    
    return results
    
}

To group an array’s anagrams into arrays within a result array, one method starts with creating a container for the groups, then looping the strings in the input array. At every step find a standardAnagram key for the string by turning it into an array, sorting it, and joining it. Add this key to groups if it does not already exist, then add the original string as a value. Once the loop is complete, return all the array values in groups.


// typeof strs === "object" (array of strings)

const groupAnagrams = (strs) => {

    const groups = {}
    
    for (let str of strs){
        const standardAnagram = str.slice().split('').sort().join('')
        if (!groups[standardAnagram]) groups[standardAnagram] = []
        groups[standardAnagram].push(str)
    }
    
    return Object.values(groups)

}

Given a string and a count of times you can change a character, what is the longest substring (aka contiguous string inside the parent) that contains all the same letter? One solution involves a counts object, setting a startIndex to 0, and a greatestCountSeen to 0. Loop the input string. At every step, update the counts object to include the count of the current character. Then for that character’s count, udate greatestCountSeen if needed. Next, if the greatestCountSeen plus the number of times you can change a character is less than the range from the current index to the startIndex, inclusive, remove a count from the character at startIndex and increase startIndex by 1. Finally, update a best if the length of the substring between startIndex and the current index, inclusive, is better. Whenever greatestCountSeen is updated, you have reached a location with a candidate for the new best, so you increment startIndex until you have a valid possible length.


// typeof s === "string"
// typeof k === "number"

const characterReplacement = (s, k) => {
    
    const counts = {}
    let startIndex = 0
    let greatestCountSeen = 0
    let best = 0
    for (let i = 0; i < s.length; i++){
        counts[s[i]] = counts[s[i]] + 1 || 1

        if(counts[s[i]] > greatestCountSeen) greatestCountSeen = counts[s[i]]

        while(i - startIndex + 1 > k + greatestCountSeen){
            counts[s[startIndex]]--
            startIndex++
        }

        best = Math.max(best, i - startIndex + 1)

    }
    return best

}

To find the largest number that can be made out of non-negative integers in an array, you can stringify the numbers and sort them by paired concatenations, such that numbers that when leading the pair make for larger joined numbers, end up on the left.


// typeof nums === "object" (array of numbers)

const largestNumber = (nums) => {
    const result = nums
    .map(value => String(value))
    .sort((a, b) =>  a.concat(b) > b.concat(a) ? -1 : 1)
    .join("")

    if(result[0] === "0") return "0"

    return result

}

How can you encode and decode an array of strings into and out of a single string? To encode, you can prefix each string with its length and a separator symbol, and then join them. To decode, you can read numbers until you get to the separator symbol, slice the number of characters after the separator symbol equal to the number just read, and add it to a result array. Then as long as the string being decoded still has length, read the next characters for numbers until a separator symbol is reached, etc.


// typeof strs === "object" (array of strings)
// typeof s === "string"

const encode = (strs) => {

  const result = []
  for (let string of strs){
    const encoded = String(string.length) + '/' + string
    result.push(encoded)
  }
  return result.join("")

}

const decode = (s) => {

  const result = []
  let i = 0
  while (i < s.length){
      let numberIndex = i
      while (s[numberIndex] !== '/'){
        numberIndex++
      }
      let size = Number(s.slice(i,numberIndex))
      const word = s.slice(numberIndex + 1, numberIndex + 1 + size)
      i = numberIndex + 1 + size
      result.push(word)
  }

  return result

}

To find the minimum length substring (adjacent characters) in a parent input string such that every character if a different string is present, you can begin by constructing a characterCountMap from the string that has the requirements for the result string. Then you can loop the parent string. At every location, check if the characterCountMap has the location’s character. If not, you can skip to the next character. Else, reduce the count at the appropriate characterCountMap by 1. If this newCount is 0, increment a zeroCounter declared outside the loop. While the zeroCounter is the same size as the characterCountMap, indicating that all characters in the requirements string are in the currently viewed string up to their count, find the startCharacter at a startIndex seeded outside the first loop at 0. If this character is an entry in the characterCountMap (indicating that its removal will be significant), add 1 to the character’s location in the characterCountMap. If this increasedCount is 1, meaning that when it is added back the characterCountMap no longer meets the result requirements, decrement the zeroCounter. If the increasedCount is 1 and the length between the index in the outermost loop and the startIndex is less than a mininimim started outside the loops at Infinity, update minimum and bestResultStartIndex and bestResultEndIndex. As the last step in the loop checking if zeroCounter matches the size of the characterCountMap, increase startIndex by 1. At the end of the loop of the parent string, use the bestResultStartIndex and bestResultEndIndex to return the result from the parent string, unless minimum is still infinite. In that case you can return an empty string.


// typeof s === "string"
// typeof t === "string"

const minWindow = (s, t) => {
    
  const characterCountMap = new Map()
  for (const char of t) {
    characterCountMap.set(char, (characterCountMap.get(char) || 0) + 1)
  }

  let zeroCounter = 0
  let startIndex = 0

  let minimum = Infinity
  let bestResultStartIndex = 0
  let bestResultEndIndex = 0

  for (let i = 0; i < s.length; i++) {
    const character = s[i]
    if (!characterCountMap.has(character)) continue

    const newCount = characterCountMap.get(character) - 1
    characterCountMap.set(character, newCount)

    if (newCount) continue

    zeroCounter++

    while (zeroCounter === characterCountMap.size) {

        const startCharacter = s[startIndex]

        if (characterCountMap.has(startCharacter)) {
            const increasedCount = characterCountMap.get(startCharacter) + 1
            characterCountMap.set(startCharacter, increasedCount)
            if (increasedCount === 1) {
                zeroCounter--
                if (i - startIndex < minimum) {
                    minimum = i - startIndex + 1
                    bestResultStartIndex = startIndex
                    bestResultEndIndex = i
                }
            }
            
        }

        startIndex++
        
    }


  }
  return isFinite(minimum) ? s.slice(bestResultStartIndex, bestResultEndIndex + 1) : ""

}

To find all pairs of strings in an array of unique strings such that the joining of the strings makes a palindrome (order matters), you can start by looping the strings and creating a trie where the words will be stored backwards, and an.index flag will also be stored at the level as a .end flag. Next, to handle a the case where there is a string with no characters, simply add an array of length 2 with the empty string’s index and the index of every word with a palindrome to a results array (the palindrome with the string indicies in the other direction will be handled later). Now the main looping of the input word list can take place. For each string, first, run a new helper function such that the string is reviewed up to just under its length (not including the last character), and if any .end is found along the way, check to see if the remainder of the parent string isPalindrome, and if so, push the index of the string in the loop and then the string being found to results. In this helper, return the node that would contain the .end after the full length of the word if it exists, otherwise return nothing. Back in the main looping of the string list, if it was found that some string at least the full length of the string being iterated exists, run a final findPalindromes helper. If the node returned in the earlier helper has a .end, the index here does not match the index of the string being reviewed, and a suffix started at an empty string isPalindrome, add the index of the string being reviewed followed by the index of the found word to results. Then iterate through the characters in the node and for each call findPalindromes recursively, building up a suffix string from various found characters. At the end return results


// typeof words === "object" (array of strings)

const palindromePairs = (words) => {
    
	const trie = {}
	const results = []

	const insertReversed = (words) => {
		for (let i = 0; i < words.length; i++) {
			const word = words[i]
			let node = trie
			for (let j = word.length - 1; j >= 0; j--) {
				const character = word[j]

				if (!node[character]) node[character] = {}

				node = node[character]
			}
			node.end = true
			node.index = i
		}
	}
    insertReversed(words)

    const isPalindrome = (word) => {
        for (let i = 0; i < word.length / 2; i++){
            if (word[i] !== word[word.length-1-i]) return false
        }
		return true
	}

	const handleEmptyString = (root) => {
        if (!root.end) return

        for (let i = 0; i < words.length; i++) {
            const validPalindrome = isPalindrome(words[i])

            if (root.index === i) continue
            if (validPalindrome) results.push([i, root.index])
        }
	}
    handleEmptyString(trie)

	const getLastPrefixNode = (word, i) => {
		let node = trie

		for (let j = 0; j < word.length; j++) {
			const character = word[j]
			const child = node[character]

			if (!child) return
			if (child.end && j < word.length - 1) {
				const suffix = word.slice(j + 1)
				const validSuffix = isPalindrome(suffix)

				if (validSuffix) results.push([i, child.index])
			}

			node = node[character]
		}

		return node
	}

	const findPalindromes = (node, suffix, i) => {
		if (node.end && node.index !== i) {
			const validSuffix = isPalindrome(suffix)

			if (validSuffix) results.push([i, node.index])
		}

		for (const char in node) {
			findPalindromes(node[char], suffix + char, i)
		}
	}


	for (let i = 0; i < words.length; i++) {
		const lastPrefixNode = getLastPrefixNode(words[i], i)
		if (lastPrefixNode) findPalindromes(lastPrefixNode, "", i)
	}

	return results

}

Binary Trees

One of the ways LeetCode has implemented a binary tree node is like this:



function TreeNode(val, left, right) {

     this.val = (val===undefined ? 0 : val)

     this.left = (left===undefined ? null : left)
     this.right = (right===undefined ? null : right)

}

Inverting a binary tree means to “turn it over” as if it was a page in a book, creating what the original would look like from behind. This means swapping every pair of left and right references. A recrusive implementation step returns immediately on a null node, and otherwise returns after using a holding variable for one of the left or right references to help complete the second half of the swap.


// typeof root === "object" (binary tree node)

const invertTree = (root) => {

    if(!root) return root
    const hold = root.right
    root.right = root.left
    root.left = hold

    invertTree(root.left)
    invertTree(root.right)

    return root

}

A height-balanced binary tree differs in depth by no more than one at each node. To test if a binary tree is height-balanced, do depth-first search from the root node, returning 0 on each base case of a null root, and otherwise check if the differences in return height values from the left or right nodes is not greater than 1. Because each hopeful step of the depth-first search then returns the maximum of the right or left depth plus 1, the full depth under any node will always bubble up. If a difference greater than one bubbles up, -1 can be returned preferentially as a signal for a non-height balanced tree.


// typeof root === "object" (binary tree node)

const isBalanced = (root) => {

    const height = (root) => {
        if(!root) return 0
        const left = height(root.left)
        const right = height(root.right)

        if(left === -1 || right === -1 || Math.abs(left - right) > 1) return -1

        return 1 + Math.max(left,right)
    }

    return height(root) !== -1

}

To find the diameter of a binary tree, or the maximum distance between any two nodes, recursion and depth-first search lead to a solution. At every node, find the maximum distance under the right and left nodes, then update the max if the sum of these is greater than the max. Return the greater of the left or right plus 1 (the plus 1 accounts for the edge between the returning node and the next node up). The base case, reached when recursion decends to find no node, returns 0.


// typeof root === "object" (binary tree node)

const diameterOfBinaryTree = (root) => {

    let max = 0
    
    const go = (loc) => {
        if(!loc) return 0

        let left = go(loc.left)
        let right = go(loc.right)

        max = Math.max(max,left+right)

        return 1+Math.max(left,right)
    }
    
    go(root)
    return max

}

To find the maximum depth of a binary tree, counted in number of nodes, you can use recursion with a base case of returning 0 on an empty node. On every other node, the greater of the depth of the left side of the tree plus 1 or the depth of the right side of the tree plus 1 should be returned.


// typeof root === "object" (binary tree node)

const maxDepth = (root) => {

        if (!root) return 0

        return 1 + Math.max(maxDepth(root.left), maxDepth(root.right))
    
}

Two binary trees have the same structure and values if at every node, their values match, and their left and right subtrees match. This lends nicely to a recursive structure which can return true on matching empty nodes and bubble a true all the way up to the final return only if everything matches.


// typeof p === "object" (binary tree node)
// typeof q === "object" (binary tree node)

const isSameTree = (p, q) => {
    
        if(!p && !q) return true
        if(p && !q || q && !p || q.val !== p.val) return false

        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right)

}

To detect if a binary tree is symmetric, first note that an empty tree is symmetric. Next, generate two pointers, one to the .left node and the other to the .right node of the head, and put these in a recursive call. These .left and .right nodes are in locations that should be symmetric and identical. If both are empty, the base case can bubble up true, if they are different by presence/absence or value, the base case can bubble upp false, and otherwise, two more recrusive calls can be generated, comparing the outer two nodes that are children of the pair that were checked, and also comparing the inner two nodes. Any false in a base case should ultimately return false in the parent function, otherwise the function returns true.


// typeof root === "object" (binary tree node)

const isSymmetric = (root) => {

    if (!root) return true

    return equal(root.left,root.right)

}

const equal = (l, r) => {
    if (!l && !r) return true
    if (!l || !r || l.val !== r.val) return false
    return equal(l.right,r.left) && equal(l.left,r.right)
}

To find if a tree subRoot is truly a subroot of a tree root, one approach is to check if any node in or below root decends to a structure that matches subRoot. However, this approach is )O(root*subRoot). Constructing a subRoot serialization and then checking if this is a substring of root can be done with any string-matching algorithm to be used at the end, including the JavaScript-native includes method, which could theoretically have an implementation that is in O(root + subRoot) time at the cost of a precompute table, like the Knuth-Morris-Pratt String Matching Algorithm .


// typeof root === "object" (binary tree node)
// typeof subRoot === "object" (binary tree node)

const isSubtree = (root, subRoot) => stringBuilder(root).includes(stringBuilder(subRoot))

const stringBuilder = (node) => {
    if(!node) return '.'
    const s = `<${stringBuilder(node.left)} + ${node.val} + ${stringBuilder(node.right)}>`
    return s
}

Returning a level order traversal of binary tree nodes (array of array of values for each level) can be done with three variables. Assuming there is a root, define result as an empty array inside an array, define a level array that contains root, and define a nextLevel array that is empty. The level array will be treated as a queue and repeatedly processed starting from the left, with each value pushed into the last inner array in result, and with each present .left and .right node being pushed in that order to the nextLevel array. When level is empty and nextLevel has length, push an empty array into result to represent the next level and replace level with nextLevel.


// typeof root === "object" (binary tree node)

const levelOrder = (root) => {

    if(!root) return []
    const result = [[]]
    let level = [root]
    let nextLevel = []

    while(level.length){
        const test = level.shift()
        result[result.length-1].push(test.val)

        if(test.left) nextLevel.push(test.left)
        if(test.right) nextLevel.push(test.right)

        if(!level.length && nextLevel.length){
            result.push([])
            level = nextLevel
            nextLevel = []
        }
    }
    return result

}

To find the lowest common ancestor of two uniquely-valued binary tree nodes, an implementation involves an inner function go. Run this function taking an argument of the root of the tree. When go runs, if there is no argument, return 0, else create a count variable and add to it the results of running go on the .left and .right of the current node, as well as 1 if the .val of the current node if one of the two values being investigated. If count is then 2, set a result variable outside of go to the current node, and return 0 so that no higher nodes will falsely be recorded as the lowest common ancestor. Otherwise return count.


// typeof root === "object" (binary tree node)
// typeof p === "object" (binary tree node)
// typeof q === "object" (binary tree node)

const lowestCommonAncestor = (root, p, q) => {

    let result
    
    const go = (node) => {
        if(!node) return 0
        let count = 0
        count += go(node.left)
        count += go(node.right)
        count += (node.val === p.val || node.val === q.val)
        if(count === 2){
            result = node
            return 0
        }
        return count
    }
    
    go(root)
    return result

}

To find an array of all the rightmost elements from top to bottom in a binary tree, run a helper function go with the root node and the current depth (starting at 0). In go, if there is no node, return immediately. Else place the .val of the current node at the depth index of results and call go twice, with the .left and then the .right nodes, each with depth+1 as the second argument. Because rights are being called after lefts, each row will be viewed from left to right even though the depth-first search nature of the algorithm means that a row may not be finished before a call goes down to the next level.


// typeof root === "object" (binary tree node)

const rightSideView = (root) => {

    const result = []

    const go = (node, depth) => {
        if (!node) return
        result[depth] = node.val
        go(node.left, depth+1)
        go(node.right, depth+1)
    }

    go(root,0)
    return result
    
}

How to construct a binary tree from arrays with unique values containing the preorder and inorder traversal? Note that in the preorder traversal, head nodes appear before children nodes. This means that the 0th location in the preorder array should be the root value of the binary tree. To construct the tree formally, return a helper function construct with four arguments – the leftmost and rightmost indicies of the preorder and inorder arrays. If either left is greater than than their right, construct can immediately return a null node. Else, get the current root node value from the value of preorder at preorderLeft. Start a new node with the value, and find the index of that value in inorder. Count the number of leftNodes by subtracting the inorderLeft from index, then build out the .left and .right nodes by running two new construct invocations with updated bounds.


// typeof preorder === "object" (array of numbers)
// typeof inorder === "object" (array of numbers)

const buildTree = (preorder, inorder) => {
    
    const construct = (preorderLeft, preorderRight, inorderLeft, inorderRight) => {
        if (preorderRight < preorderLeft || inorderRight < inorderLeft) return null

        const value = preorder[preorderLeft]
        const node = new TreeNode(value)
        const index = mapValueToInorderIndex.get(value)

        const leftNodes = index - inorderLeft
        node.left  = construct(preorderLeft + 1, preorderLeft + leftNodes, inorderLeft, index - 1)
        node.right = construct(preorderLeft + leftNodes + 1, preorderRight, index + 1, inorderRight)

        return node
    }

    const mapValueToInorderIndex = new Map()
    for (let i = 0; i < inorder.length; i++){
        mapValueToInorderIndex.set(inorder[i], i)
    }
    
    return construct(0, preorder.length - 1, 0, inorder.length - 1)

}

To find all root-to-leaf paths in a binary tree that sum to a target value, you can create a results array and then use recursion with a helper function. Run the helper function with the root, set a sum of 0, and a valsSoFar as an empty array. No root is the base case so then you can just return. Otherwise, create a currentVals array out of valsSoFar and the value at the current node, and create a newSum by adding sum to the value at the current node. Then, if current node is a leaf node (has no descendents), and the newSum is the target sum, you can push currentVals to results. Else recurse on the .left and .right of the current node.


// typeof root === "object" (binary tree node)
// typeof targetSum === "number"

const pathSum = (root, targetSum) => {

    const results = []
    
    const go = (root, sum = 0, valsSoFar = []) => {
        if(!root) return
        const currentVals = [...valsSoFar, root.val]
        const newSum = sum + root.val

        if(!root.left && !root.right && targetSum === newSum) results.push(currentVals)
        go(root.left, newSum, currentVals)
        go(root.right, newSum, currentVals)
    }
    
    go(root)
    
    return results

}

How to find the maximum width of a binary tree? One solution involves depth-first search. Create an empty leftmostLocations array, then conduct a depth-first search starting with the root node, a widthLocation of 0, and a depth of 0. The base case is a null node, but otherwise, a helper function can update the leftmostLocations array with the current widthLocation at the depth index if the depth has never before been seen. You can then create an indexNormalizedToLeftmost but subtracting the leftmost location at the given depth from the widthLocation (which helps with large LeetCode test cases), update a best with indexNormalizedToLeftmost+1 if appropriate, and run the helper on the .left and .right of the current node, also passing down the doubled indexNormalizedToLeftmost and the doubled indexNormalizedToLeftmost with a 1 added after the multiplication, respectively. The third argument for both depth-first search calls is simply depth+1.


// typeof root === "object" (binary tree node)

const widthOfBinaryTree = (root) => {
    
  const leftmostLocations = []
  let best = 0

  const dfs = (node, widthLocation, depth) => {
    if (!node) return
    if (depth === leftmostLocations.length) {
      leftmostLocations[depth] = widthLocation
    }
    
    const indexNormalizedToLeftmost = widthLocation - leftmostLocations[depth]
    best = Math.max(best, indexNormalizedToLeftmost + 1)
    dfs(node.left, indexNormalizedToLeftmost*2, depth + 1)
    dfs(node.right, indexNormalizedToLeftmost*2+1, depth + 1)
  }
  
  dfs(root, 0, 0)
  return best

}

To return the values of the nodes of a binary tree in zigzag order, that is, a first row left-to-right, followed by a second row right-to-left, and third row left-to-right, etc., you can perform a level-order traversal and keep track of the row number to reverse the values of every other row. If there is input, create a results array, fill a row array with the starting node, and declare an empty nextRow array. While row has a length, create a values array and loop the nodes in row, pushing every value seen to values, and pushing every .left and .right node seen, in that order, to nextRow. If the results array is an odd length, reverse the values array, and in either case, next push it to the results array. Then set row as nextRow and set nextRow as an empty array. This is the last part of the outer cycle. It continues if the new row contains any values.


// typeof root === "object" (binary tree node)

const zigzagLevelOrder = (root) => {
    if (!root) return []

    const results = []
    let row = [root]
    let nextRow = []

    while(row.length){

        let values = []
        
        for (let node of row){
            values.push(node.val)
            if (node.left) nextRow.push(node.left)
            if (node.right) nextRow.push(node.right)
        }
        if (results.length % 2) values = values.reverse()

        results.push(values)
        row = nextRow
        nextRow = []
    }

    return results
    
}

To find all possible ways a binary tree sums to a given number on a downwards path (no bending at a node), you can run a recursive call with default parameters for sumsToHere, which starts as an empty array, as well as a result count that starts at 0. If there is no node, return result, else push a 0 value to sumsToHere and loop the sumsToHere array. At every step, add the value at the current node, and then check if the new value at the sumsToHere location is equal to target value. If so, add 1 to the result. Next, run the parent recursively on the .left and .right of the current node, and add their values to result. Make sure to copy the sumsToHere array, rather than put it in directly. Finally, return result.


// typeof root === "object" (binary tree node)
// targetSum === "number"

const pathSum = (root, targetSum, sumsToHere = [], result = 0) => {

        if(!root) return result

        sumsToHere.push(0)
        for(let i = 0; i < sumsToHere.length; i++){
            sumsToHere[i] += root.val
            if(sumsToHere[i] === targetSum) result++
        }

        result += pathSum(root.left, targetSum, sumsToHere.slice(), 0)
        result += pathSum(root.right, targetSum, sumsToHere.slice(), 0)

        return result
        
}

To return an array of nodes that all are a certain distance from a target node in a binary tree, first note that an empty root returns an empty array. Otherwise, you can traverse the array from the root until you find the targetNode, adding reverse .parent links at every step. Then you can run recursion from the targetNode, branching in the .left, .right, and .parent directions until either an end of the tree is reached, or a node of the desired distance from the targetNode is found. When such a node is found, you can push it to results.


// typeof root === "object" (binary tree node)
// typeof target === "object" (binary tree node)
// typeof k === "number"

const distanceK = (root, target, k) => {
  if(!root) return []

  const addReverseLinksAndReturnTargetNode  = (root, parent) => {
        if(!root) return null
        root.parent = parent
        if(root === target){    
        return root 
        }    
        return addReverseLinksAndReturnTargetNode(root.left, root) ||
         addReverseLinksAndReturnTargetNode(root.right, root)    
  }

  const targetNode = addReverseLinksAndReturnTargetNode(root, null)  
  const results = []

  const getResults = (node, distance) => {
    if(!node || node.visited) return
    if(!distance){
        results.push(node.val)
        return
    }   
    node.visited = true
    getResults(node.left, distance-1)
    getResults(node.right, distance-1)
    getResults(node.parent, distance-1)
  }

  getResults(targetNode, k)
  return results

}

To serialize and deserialize a binary tree into and out of a string, you can use helper functions. In the serialize function, you can create a collection array and run it with the root of the tree in a recursive helper. In this helper, if given node is empty, push a symbol like # to the array and return, otherwise push the .val to the array and run the helper on the .left and .right of the node, in that order. Once all helpers resolve, turn the array into a string with commas separating the values. To deserialize, you can return null if there is no data length, otherwise split the input by , and run a helper function. In the helper, whenever there is no array length, you can return null, and after splicing off the first entry in the array, if this is the null symbol #, you can also return null. Otherwise turn the spliced value into a number and add it as the .val of a new node, and then run the function recursively on the array, first assigning its results to .left of the node and then to .right. This works because the splicing means that .right will receive the correct array input. At the end of the helper, return the node and at the end of all recursive calls return the first helper function.


// typeof root === "object" (binary tree node)
// typeof data === "string"

const serializeHelper = (node, array) => {
    if (!node) return array.push("#")

    array.push(node.val)
    serializeHelper(node.left, array)
    serializeHelper(node.right, array)
}

const serialize = (root) => {
    const array = []
    serializeHelper(root, array)
    return array.toString()
}

const deserializeHelper = (array) => {
    if (!array.length) return null
    const value = array.splice(0,1)
    if (value[0] === "#") return null

    const node = new TreeNode(Number(value))
    node.left = deserializeHelper(array)
    node.right = deserializeHelper(array)
    return node
}

const deserialize = (data) => {
    if(!data?.length) return null
    return deserializeHelper(data.split(","))
}

To find the maximum sum of a path in a binary tree, you can ue a helper function. Set a best to negative Infinity and run a recursive helper function with the root. In the recusive function, if the node is empty, return 0 as the base case. Else, find the bestLeft as the max of 0 or the helper function run recusively with .left of the root, and then find bestRight a the max of 0 or the helper function run recursively with .right of the root. Then compute a bestAtNode with the sum of bestLeft, the value at the current node, and bestRight. If bestAtNode is greater than the best outside the helper function, update best. Finally, return the maximum of bestLeft or bestRight plus the value at the current node (since you can only pass up the maximum sum of one path). When all runs of the helper function finish, return best.


// typeof root === "object" (binary tree node)

const maxPathSum = (root) => {
    let best = -Infinity
    
    const go = (node) => {
        if(!node) return 0

        const bestLeft = Math.max(0, go(node.left))
        const bestRight = Math.max(0, go(node.right))
        const bestAtNode = bestLeft + node.val + bestRight

        if (bestAtNode > best) best = bestAtNode
        return Math.max(bestLeft, bestRight) + node.val
    }
    
    go(root)
    return best

}

Binary Search

Sorted information can be investigated in logarithmic (log(n)) time. Binary search is logarithmic. Each step divides searchable space in half–much faster than a linear search.

In a basic kind of binary search on an array, start and end variables point to the first and last filled indexes of the array. While end is not smaller than start, define a new mid as the midpoint between start and end (rounding allowed). Mid matching a target value allows immediate return of the value. If the target value is less than the mid index value, use mid-1 as the new end, and if the target value is greater, use mid+1 as the new start. Next, test if the new end is smaller than start to try another search step. If the search ends without finding the target value, returning -1 can indicate this (returning something else is better if negative targets are allowed).


// typeof nums === "object" (array of numbers)
// typeof target === "number"

const search = (nums, target) => {

    let start = 0
    let end = nums.length - 1

    while(start <= end){
        const mid = Math.floor((end-start)/2) + start
        if (target === nums[mid]) return mid

        if (nums[mid] > target){
            end = mid - 1
        } else {
            start = mid + 1
        }
    }

    return -1

}

A version of binary search involves finding the first instance of a change in a series (such as first bad version). The start and end indices are initialized as before, as is mid within a while loop, and since there is a guarantee of a change the loop will end by finding an index. The test condition uses an externally-provided function to see if the mid index has the change – if so, end is set to mid (as this is a candidate for the first change), and if this index is the only index remaining, mid can be returned. Else the mid index and everything lower cannot be the result, so start can be mid+1 for the next loop.


// typeof isBadVersion === "function"
// typeof n === "number"

const solution = function(isBadVersion) {

    return function(n) {
        
        let start = 1
        let end = n

        while(true){

            const mid = start+Math.floor((end-start)/2)

            if(isBadVersion(mid)){
                end = mid
                if(start === end) return mid
            } else {
                start = mid + 1
            }

        }
        
    }
}

How to find a number in an array that was divided into two segments that were swapped and reattached, but was before that sorted? To solve this in logarithmic time, begin as if the array was sorted normally, and declare left and right pointers at either end of the array. While left is less than or equal to right, define a mid pointer between the two (round either way consistently). If mid is the value being searched for, you can return the index. Otherwise, notice that one of the section above and the section below the mid must be in sorted order. To detect which, check if the left-most value is less than the value at mid – this means the left side is sorted. Otherwise the right side is sorted. If the left side is sorted, and the searched-for value is less than the value at mid but greater or equal to the left-most value, the left side is the side to investigate – update the right pointer to mid-1. Otherwise, if the left side is sorted, the left pointer should go to mid+1 by process of elimination. If instead it is the right side that is sorted, use similar code to check if the the searched-for value is on the sorted or non-sorted side. You can return -1 if it is never found.


// typeof nums === "object" (array of numbers)
// typeof target === "number"

const search = (nums, target) => {

  let left = 0
  let right = nums.length - 1

  while (left <= right) {
    const mid = left + Math.ceil((right - left) / 2)
    if (nums[mid] === target) return mid

    if (nums[left] < nums[mid]) {
      if (nums[left] <= target && target < nums[mid]) {
        right = mid - 1
      } else {
        left = mid + 1
      }
    } else {
      if (nums[mid] < target && target <= nums[right]) {
        left = mid + 1
      } else {
        right = mid - 1
      }
    }
  }

  return -1

}

To create a structure that can store multiple values at a key and return a given one based on timestamp, set the data structure variable to a function () {} that contains this.map. Adding set to the prototype of the data structure, involves checking if the key already exists on the map. If not, set the key to an empty array. Otherwise, push an object with the timestamp and value to the key. Adding get can involve binary search. If the key does not exist, you can return an empty string, else get the timestamps array to be searched off of the key, and set left and right to the edges of the array. While left is less than right, compute mid, and check if the timestamp at mid is less than the timestamp coming from the set argument. If so, set left to mid+1, else right can be mid. When the pointer cross, check if timestamp at left is less than or equal to the timestamp coming from the set argument. If so, return its value. Otherwise, do the same for left-1, and if a timestamp there also does not work, you can return an empty string.


// typeof timestamp === "number"

const TimeMap = function() {
  this.map = new Map()
}

TimeMap.prototype.set = function(key, value, timestamp) {
  if (!this.map.has(key)) this.map.set(key, [])
  this.map.get(key).push({ timestamp, value })
}

TimeMap.prototype.get = function(key, timestamp) {
    if (!this.map.has(key)) return ""
    const timestamps = this.map.get(key)
    let left = 0
    let right = timestamps.length - 1
    
    while (left < right) {
        const mid = left + Math.floor((right - left) / 2)
        if (timestamps[mid].timestamp < timestamp) {
            left = mid + 1
        } else {
            right = mid
        }
    }
    
    if (timestamps[left] && timestamps[left].timestamp <= timestamp) return timestamps[left].value
    if (timestamps[left - 1] && timestamps[left - 1].timestamp <= timestamp) return timestamps[left - 1].value

    return ""
}

To find if a number exists in a matrix of ordered numbers such that each row is sorted in ascending order and rows that have greater indicies contain greater values, two rounds of binary search can be conducted. First, find the row that would contain the number by using the maximum and minimum row indicies as yRight and yLeft. Test if the value at the end of the row of a floored mid is less than the target to increase yLeft to mid+1, else yRight becomes mid. Once the candidate row has been determined, you can return false immediately if the target value is outside the bounds of the row. Else, conduct a second binary search on the row. If the search is narrowed down the a single candidate that is not the target value, return false, else return true.


// typeof matrix === "object" (array of arrays of numbers)
// typeof target === "number"

const searchMatrix = (matrix, target) => {

    let yLeft = 0
    let yRight = matrix.length - 1

    while (yLeft < yRight){
        const mid = yLeft + Math.floor((yRight-yLeft)/2)
        if (matrix[mid][matrix[0].length-1] < target){
            yLeft = mid + 1
        } else {
            yRight = mid
        }
    }
    
    if (target > matrix[yLeft][matrix[0].length-1] || target < matrix[yLeft][0]) return false
    
    let xLeft = 0
    let xRight = matrix[0].length - 1

    while(xLeft < xRight){
        const mid = xLeft + Math.floor((xRight-xLeft)/2)
        if (matrix[yLeft][mid] < target){
            xLeft = mid + 1
        } else {
            xRight = mid
        }
    }

    return matrix[yLeft][xLeft] === target

}

To find the minimum in a rotated sorted array of unique values, you can start by noticing that if you divide the array repeatedly into halves, if the value at mid in a binary search is ever greater than the value at right, the left side of the search space is sorted and the right side is not. This means that you can move the left to mid+1. Otherwise, you can move the right to mid. When the left and right pointers intersect the value at either pointer is the lowest.


// typeof nums === "object" (array of numbers)

const findMin = (nums) => {

  let left = 0
  let right = nums.length - 1

  while (left < right) {
    const mid = left + Math.floor((right-left)/2)
    if (nums[mid] > nums[right]){
        left = mid + 1
    } else {
        right = mid
    }
  }

  return nums[left]
  
}

To find the maximum profit when given job start times, end times, and the profit for each, one way to start is to organize all of these inputs into an array of arrays with three numbers, and then sort this jobs array by end time from least to greatest. Next, a dp array can be started as an array with a single value, the profit of the job sorted to end first. Every other location in the jobs array can then be iterated. For each step in this loop, first save the profit at the current location. Then use binary search on the portion of the jobs array to the left of the current location in the iteration. You can stop the binary search when the pointers cross, setting left to mid+1 whenever the end of the job at mid is less than or equal to the start of the job being pointed to in the outer loop, and setting right to mid-1 otherwise. This means that the left pointer might point to a job that overlaps with the one in the outer iteration, so when the binary search is done, the right pointer will be pointing to the leftmost job that does not overlap with the one indicated in the iteration. After the binary search, if right is not -1, you can update profit by the value in the dp location indicated by right (the greatest profit counted previously that does not overlap with the profit in the current location). Finally for this step in the loop, update the dp location being iterated to the best of either the profit or the value in dp one index lower. At the end of the iteration, return the rightmost value in dp.


// typeof startTime === "object" (array of numbers)
// typeof endTime === "object" (array of numbers)
// typeof profit === "object" (array of numbers)

const jobScheduling = (startTime, endTime, profit) => {

    const jobs = []
    for (let i = 0; i < startTime.length; i++) {
        jobs.push([startTime[i], endTime[i], profit[i]])
    }
    jobs.sort((a,b)=>a[1]-b[1])

    const dp = [jobs[0][2]]

    for (let i = 1; i < jobs.length; i++) {
        let profit = jobs[i][2]
    
        let left = 0
        let right = i - 1
        let mid = left + Math.floor(right-left)
        while(left <= right){
            mid = left + Math.floor(right-left)
            if(jobs[mid][1] <= jobs[i][0]){
                left = mid + 1
            } else {
                right = mid - 1
            }
        }

        if (right != -1) profit += dp[right]
        dp[i] = Math.max(profit, dp[i-1])
    }
    
    return dp[jobs.length-1]

}

To find the median of two sorted arrays in logarithmic time, you can first note two cases, one where the sum of the array lengths is odd, and the other where the sum of the array lengths is even. If the lengths are odd, you can run a helper using a centralJoinedIndex of the floored sum of the lengths divided by 2, and return it directly. If the lengths are even, you can run the same helper twice, targeting a centralJoinedIndex computed the same and another with a centralJoinedIndex of one less, returning a value halfway between the two values. In the helper, parameters are available for the left and right indices of both arrays being considered, as well as the arrays themselves, and the given centralJoinedIndex. If the left of either array has crossed over its right, this is a sign that the result in that helper function is in the other array at a location equal to the centralJoinedIndex minus the left index that crossed over. Otherwise, compute floored middle indicies and values for both arrays, and handle four cases for four possible recursive calls. If the sum of both middle indicies is less than the target index, the next recursive call can remove the front half of whichever array has a smaller middle value, replacing its starting index with its middle index plus 1. Left is only moved if we are sure we can eliminate a whole section, because of its role in returning a result. However, if the sum of both middle indicies is greater or equal to the centralJoinedIndex, a similar pattern can be followed for the final two cases. The right index of whichever array’s middle value is greater is overridden with its middle index minus 1. This is the full algorithm. The reason the end result is in a location in the array without crossed pointers at the centralJoinedIndex minus a displacement equal to the left of the removed part of the other array is because, firstly, the crossed array has been eliminated from consideration. Secondly, the target must be the centralJoinedIndex less some value because that much of the “pre-mid” has already been found in the eliminated array. Edge cases of an array with no length should be handled gracefully by the existing algorithm with the moving of a right pointer backwards.


// typeof nums1 === "object" (array of numbers)
// typeof nums2 === "object" (array of numbers)

const findMedianSortedArrays = (nums1, nums2) =>{

        const len1 = nums1.length
        const len2 = nums2.length

        if ((len1 + len2) % 2){
            return get_kth(nums1, nums2, 0, len1-1, 0, len2-1, Math.floor((len1+len2)/2))
        }

        const mid1 = get_kth(nums1, nums2, 0, len1-1, 0, len2-1, Math.floor((len1+len2)/2))
        const mid2 = get_kth(nums1, nums2, 0, len1-1, 0, len2-1, Math.floor((len1+len2)/2)-1)

        return (mid1 + mid2) / 2
}

const get_kth = (nums1, nums2, start1, end1, start2, end2, centralJoinedIndex) => {
        if (start2 > end2) return nums1[centralJoinedIndex-start2]
        if (start1 > end1) return nums2[centralJoinedIndex-start1]
        
        const mid1 = Math.floor((start1 + end1)/2)
        const mid2 = Math.floor((start2 + end2)/2)
        const mid1Value = nums1[mid1]
        const mid2Value = nums2[mid2]
        
        if ((mid1 + mid2) < centralJoinedIndex) {
            if(mid1Value > mid2Value) return get_kth(nums1, nums2, start1, end1, mid2+1, end2, centralJoinedIndex)
            return get_kth(nums1, nums2, mid1+1, end1, start2, end2, centralJoinedIndex)
        }


        if (mid1Value > mid2Value) return get_kth(nums1, nums2, start1, mid1-1, start2, end2, centralJoinedIndex)
        
        return get_kth(nums1, nums2, start1, end1, start2, mid2-1, centralJoinedIndex)

}

Graphs

A two-dimensional array can represent a screen of pixels. To represent the “flood fill” tool in a paint editor, identify the coordinates at which you wish to begin flood fill. If that location is already the desired color, stop the process. Otherwise, you can begin a depth-first search at that location, replacing its color with the target color, and generating recursive calls up, down, left, and right. In each of these recursive calls, if the coordinates are out of bounds or the color is not the color being overwritten, handle this base case by not recursing further from that point. Otherwise, change the color at the location and generate more recursive calls up, down, left, and right.


// typeof nums === "object" (array of numbers)
// typeof target === "number"

const floodFill = (image, sr, sc, color) => {

    const start = image[sr][sc]
    if (start === color) return image

    const go = (r,c) => {
        if(r < 0 || c < 0 || r === image.length || c === image[0].length || image[r][c] !== start) return
        image[r][c] = color
        go(r+1,c)
        go(r-1,c)
        go(r,c-1)
        go(r,c+1)
    }

    go(sr,sc)
    return image

}

How to modify a binary matrix to return the closest 0 for each cell? This is a problem that can be solved with breadth-first search. After creating a queue, a triplet with row, column, and distance from a 0 can be pushed to the queue for every 0 found in the array (so all distances start as 0). Every other value can be set as Infinity to make it clear which cells have not yet been processed. Then, while the queue has a length, items can be shifted off the front, the cells they point to can be updated with a distance, and new items can be pushed to the queue and processed with distance + 1 and coordinates just up, down, left, and right (but not out of bounds). After shifting, do not further process a queue item if has a non-infinity value at the time it is shifted in.


// typeof mat === "object" (array of arrays of numbers)

const updateMatrix = (mat) => {
        
    const queue = []
    
    for(let row = 0; row < mat.length; row++) {
        for(let column = 0; column < mat[0].length; column++) {

            if(!mat[row][column]){
                queue.push([row, column, 0])
            }
            mat[row][column] = Infinity
        }
    }

    const directions = [[-1, 0], [0, -1], [1, 0], [0, 1]]
    
    while(queue.length) {
        let [row, column, distance] = queue.shift()
        if(mat[row][column] !== Infinity) continue
        
        mat[row][column] = distance
        
        for(let [x, y] of directions) {
            let newRow = x + row;
            let newColumn = y + column;
            
            if(newRow >= 0 && newRow < mat.length && newColumn >= 0 &&
            newColumn < mat[0].length && mat[newRow][newColumn] === Infinity) {
                    queue.push([newRow, newColumn, distance + 1])
            }
        }
    }
    return mat

}

To clone a graph of unique values without loops, if the graph exists, you can use recursion with a map. If any value is present in the map you can return whatever is stored there. Otherwise, make a copy of currentNode, set it by value in the map, and loop the reference to its children to recursively copy these.


// function Node(val, neighbors) {
//     this.val = val === undefined ? 0 : val;
//     this.neighbors = neighbors === undefined ? [] : neighbors;
//  }

const cloneGraph = (node) => {
    if (!node) return null
    const seen = new Map()

    const copy = (currentNode) => {
        if(seen.has(currentNode.val)) return seen.get(currentNode.val)
        const copyNode = new Node(currentNode.val, [])

        seen.set(copyNode.val, copyNode)
        for (let neighbor of currentNode.neighbors) {
            copyNode.neighbors.push(copy(neighbor))
        }

        return copyNode

    }

    return copy(node)
    
}

To detect if every course can be taken when prerequisites are given in an array of arrays of number pairs that look like [course, prerequisite], an adjacency list can be created that lets all courses under a prerequisite be pointed to from that prerequisite. Then, every course can be investigated to see if any of its descendants (from the perspective of adjacency list lookups) contain its value. This would mean a circular dependency and be the case to return false. In the depth-first searches starting from each course, caching previously seen course values prevents duplicate work, and just before any course’s descendants are investigated, it makes sense to store a flag with that course’s number, so that lower calls can check their value against the set of flags to see if returning false is needed. On any level of the recursive process, once the lower-level calls are completed, the flag should be removed, since it would no longer represent a valid ancestor.


// typeof numCourses === "number"
// typeof prerequisites === "object" (array of arrays with 2 numbers)

const canFinish(numCourses, prerequisites) {

  const cache = new Set()
  const flag = new Set()
  const adjacencyList = Array(numCourses).fill().map(r => [])
  
  for (let [course, prerequisite] of prerequisites) {
    adjacencyList[prerequisite].push(course)
  }
  
  const dfs = (course) => {
    if (cache.has(course)) return true
    if (flag.has(course)) return false
    
    flag.add(course)
    for (let nextCourse of adjacencyList[course]) {
      if (!dfs(nextCourse)) return false
    }
    flag.delete(course)
    cache.add(course)
    return true
  }

  for (let i = 0; i < numCourses; i++) {
    if (!dfs(i)) return false
  }

  return true
  
}

In order to count the number of islands in a grid (unique groups of “1"s connected up, down, left, or right to other “1"s), an approach involves iterating through all the values of the grid. When a ‘1’ is found, increment a count variable, then run a flood function to check if the location is in bounds and a “1”. If so, “sink” the space by assigning a “0”, then do depth-first search and run flood up, down, left, and right. Every connected “1” should disappear the first time one is found in the looping.


// typeof grid === "object" (array of arrays of numbers)

const numIslands = (grid) => {

  let count = 0
  
  const flood = (i, j) => {
      if (i < 0 || j < 0 || i === grid.length || 
      j === grid[0].length || grid[i][j] === '0') return
      grid[i][j] = '0'
      const directions = [[0,1],[0,-1],[1,0],[-1,0]]
      for (let [y, x] of directions) flood(i+y,j+x)
  }
  
  for (let i = 0; i < grid.length; i++){
      for (let j = 0; j < grid[0].length; j++){
          if (grid[i][j] === '1') {
              count++
              flood(i,j)
          }
      }
  }
    
    return count

}

In a grid where 1 represents a fresh orange, 2 represents a rotten orange, 0 represents an empty space, and every minute every fresh orange next to a rotten orange beomes rotten, how many minutes, if possible, does it take for all oranges to turn rotten? This problem can be solved with breadth-first search. First, iterate the values in the grid to count the number of oranges into a oranges variable. In the same iteration, for all rotten oranges, push arrays of 3 numbers each into a queue– the orange’s coordinates, and and a minutesToRotten of 0. Temporarily assign rotten oranges in the grid as 1s to make queue processing easier. For queue processing, as long as queue has a length, shift off the first item, make sure its coordinates are on the grid and point to a fresh 1 orange, then decrease the oranges counter by 1, assign the grid location as 2, maximize a depth variable by comparing it with minutesToRotten, and push new queue triplets up, down, left, and right if those are the adjacencies allowed by the question, with the new minutesToRotten increased +1 compared to the spawning item. When queue has no length, return depth if the oranges variable is 0, else return -1 to show that all oranges will never become rotten under the constraints.


// typeof grid === "object" (array of arrays of numbers)

const orangesRotting = function(grid) {

    let oranges = 0
    let depth = 0
    const queue = []
    for (let i = 0; i < grid.length; i++){
        for (let j = 0; j < grid[0].length; j++){
            if(grid[i][j]) oranges++
            if(grid[i][j] === 2){
                grid[i][j] = 1
                queue.push([i,j,0])
            }
        }
    }

    const dirs = [[-1,0],[1,0],[0,1],[0,-1]]
    while(queue.length){
        const cur = queue.shift()
        if(cur[0] < 0 || cur[1] < 0 || cur[0] === grid.length
          || cur[1] === grid[0].length || grid[cur[0]][cur[1]] !== 1){
            continue
        }
        depth = Math.max(depth,cur[2])
        oranges--
        grid[cur[0]][cur[1]] = 2
        for (let dir of dirs){
            queue.push([cur[0]+dir[0],cur[1]+dir[1],cur[2]+1])
        }
    }
    
    return oranges ? -1 : depth

}

Given an array of account arrays where the item at each inner array’s first index is a name, and every other index contains an email, how to merge accounts with shared emails? Two arrays might be connected by a third if that third shares emails on both other lists, and the chain can get even longer. In order to solve, first build a nameLookup and adjacencyList by looping through each starting account, extracting the name from the 0th index, and at each step looping through the emails associated with each starting account. Assign a name to each email in nameLookup, and use the email at the 1st index to create adjacency relationships – adding each 2nd and further index email to an adjacencyList value array of the 1st index email key, and adding the 1st index email to the adjacencyList array of every other email in the account. Reorganization complete, email keys in the adjacencyList can then be looped. For any email that has not been seen in this phase of the algorithm, a depth-first search can be run, first adding the email to seen, then seeding a new emails array with the email key being processed, then looping items in the array associated with the email in the adjacencyList. For every next email on this list that has not been seen, an additional dfs can run, and the results can be pushed to the emails array before returning it. When dfs is done, the emails array returned to the outer adjacencyList loop is a joinedAccount. Sort as needed, and use nameLookup to add a name to the front of the array before pushing it to result.


// typeof accounts === "object" (array of arrays of strings)

const accountsMerge = (accounts) => {  
    
    const nameLookup = {}
    const adjacencyList = {}
    
    for (let account of accounts) {
        const name = account[0]
        for (let i = 1; i < account.length; i++) {
            nameLookup[account[i]] = name
            if (!adjacencyList[account[i]]) adjacencyList[account[i]] = []

            if (i != 1) {
                adjacencyList[account[1]].push(account[i])
                adjacencyList[account[i]].push(account[1])
            }
        }
    }
    
    const result = []
    const seen = new Set()
    
    const dfs = (email) => {
        seen.add(email)
        const emails = [email]
        for (let nextEmail of adjacencyList[email]){
            if(!seen.has(nextEmail)) emails.push(...dfs(nextEmail))
        }        
        return emails
    }
    
    for (let email in adjacencyList) {
        if (!seen.has(email)) {
            const joinedAccount = dfs(email)
            joinedAccount.sort()
            result.push([nameLookup[joinedAccount[0]], ...joinedAccount])
        }
    }
    
    return result
}

How to find if a word exists in a grid of characters when letters of the word can be connected up, down, left, or right? You can iterate through every space on the board. For every space, run a helper function go taking the coordinates and a letterIndex variable starting at 0 as arguments. The letterIndex represents the index of the next letter in the target word that must match. Inside go, return false if the letter at the coordinates does not match the letter at the letterIndex of the word. Then, if we have reached the end of the word, return true. Otherwise, temporarily overwrite the coordinates on the grid with a nonletter character, and run recrsive calls of go up, down, left, and right on valid coordinates and the next letterIndex. If any of these return true the parent go function can also return true. Else restore the letter at the current grid coordinates and return false. This is backtracking–you only need one true path to return true overall, otherwise you can “pack up” the current path and try an adjacent path. If any go in the original grid iteration returns true, the overall function can return true, otherwise return false.


// typeof board === "object" (array of arrays of strings of 1 character)
// typeof word === "string"

const exist = (board, word) => {

  const directions = [[-1, 0], [1, 0], [0, 1], [0, -1]]
  const go = (i, j, letterIndex) => {
    if (board[i][j] !== word[letterIndex]) return false
    if (letterIndex === word.length - 1) return true

    board[i][j] = "."
    for (const [x, y] of directions) {
      const nextI = i + x
      const nextJ = j + y
      if (nextI >= 0 && nextI < board.length && nextJ >= 0 && nextJ < board[0].length) {
        if (go(nextI, nextJ, letterIndex + 1)) return true
      }
    }
    board[i][j] = word[letterIndex]
    return false
  }

  for (let i = 0; i < board.length; i++) {
    for (let j = 0; j < board[0].length; j++) {
      if (go(i, j, 0)) return true
    }
  }

  return false

}

To find all roots of trees of minimum height from a connected components graph with consistant connections that may be “bent” to start from any node, notice that a list of responses will always be the most “central.” To solve, run a number of rounds where each round removes nodes that only have one connection. The last round of pruning should contain valid root nodes. Notice than whenever there are less than 3 nodes, every node can be a root node. Otherwise, create a connectionCounts array with length equal to the number of nodes, filled to start with 0s, and a connectionLists array of the same length filled with empty arrays. For each edge pair connecting two nodes, increment each node index by 1 in connectionCounts, and push the opposite to each node index in connectionLists. Now, create another array seen with length equal to the number of nodes, and fill it with false, create a seenCount starting as 0, and an empty array as a queue. For every connectionCount of 1, flip the seen boolean to true at that location, increment seenCount by 1, and push the index to the queue. For the final part of the algorithm, repeatedly process the queue. Create a nextQueue at the start of each processing round, and loop the nodes in the queue. For each node, loop the adjacent nodes. Ignore the adjacent node if it has been seen. Otherwise, decrease its connectionCount by 1, then check if this value is 1. If so, it is now a leaf at the edge of the graph, so mark it as seen, increment the seenCount, and push it to the nextQueue. Once the queue is processed, if seenCount equals the total number of nodes, return nextQueue, else replace queue with nextQueue and start the next round of queue processing.


// typeof n === "number"
// typeof edges === "object" (array of arrays with 2 numbers)

const findMinHeightTrees = (n, edges) => {

    if (n < 3) return [...Array(n)].map((_,i)=> i)
    
    const connectionCounts = Array(n).fill(0)
    const connectionLists = [...Array(n)].map(() => [])
    
    for(let [node1, node2] of edges) {
        connectionCounts[node1]++
        connectionLists[node1].push(node2)
        connectionCounts[node2]++
        connectionLists[node2].push(node1)
    }
    
    const seen = Array(n).fill(false)
    let seenCount = 0
    let queue = []

    for(let i = 0; i < n; i++) {
        if(connectionCounts[i] === 1) {
            seen[i] = true
            seenCount++
            queue.push(i)
        }
    }
    
    while(true) {
        const nextQueue = []
        
        for(let node of queue) {
            for(let adjacent of connectionLists[node]) {
                if(seen[adjacent]) continue
                if(--connectionCounts[adjacent] === 1) {
                    seen[adjacent] = true
                    seenCount++
                    nextQueue.push(adjacent)
                }
            }
        }
        if(seenCount === n) return nextQueue       
        queue = nextQueue
    }
}

To find a list of coordinates on a grid where a downwards slope (up, down, left, and right) can be found that reaches both the upper or left edges of the grid, as well as the lower or right (which can be thought of as water flowing to two oceans), consider depth-first search. Create two new grids the same shappe as the original grid, and fill both with false. Then loop the original array. At every location on the upper or left edge of the graph, begin a depth-first search process that places true at that location in one of the helper grids, and then continues planting true in every direction up, down, left, or right direction until an edge is reached, another true is reached, or a value in the given direction on the original grid is less than the original gird value at the location that is trying to be reached. (This would indicate the slope in the given direction is not sloping down towards the upper or left sides of the grid.) Fill the other helper grid by doing the indentical process spawning fom the bottom or the right side of the grid. Finally, add to a results array every location that is true on both helper grids, and return results.


// typeof grid === "object" (array of arrays of numbers)

const pacificAtlantic = (heights) => {

    const Atlantic = [...Array(heights.length)].map(() => new Array(heights[0].length).fill(false))
    const Pacific = [...Array(heights.length)].map(() => new Array(heights[0].length).fill(false))
    const directions = [[1, 0], [-1, 0], [0, -1], [0, 1]]

    const dfs = (i, j, seen) => {
        seen[i][j] = true
        for (let direction of directions) {
            const newI = i + direction[0]
            const newJ = j + direction[1]
            if (newI < 0 || newJ < 0 || newI === heights.length || newJ === heights[0].length || seen[newI][newJ]) continue
            if (heights[newI][newJ] >= heights[i][j]) {
                dfs(newI, newJ, seen)
            }
        }
    }
    
    for (let i = 0; i < heights.length; i++) {
        for (let j = 0; j < heights[0].length; j++) {
            if (i === 0 || j === 0) {
                dfs(i, j, Pacific)
            }
            if (i === heights.length - 1 || j === heights[0].length - 1) {
                dfs(i, j, Atlantic)
            }
        }
    }
    const results = []
    for (let i = 0; i < heights.length; i++) {
        for (let j = 0; j < heights[0].length; j++) {
            if (Pacific[i][j] && Atlantic[i][j]) {
                results.push([i, j])
            }
        }
    }
    return results

}

In a grid that contains a start location, food, empty spaces, and obstacles, what is the shortest number of steps to any food cell? You can use breadth-first search starting at the start location and pushing new legal adjacent locations (not off the board or previously seen) until a food is seen. If you keep a counter of number of steps away from the origin, you can return this when you reach the food symbol. If you never do you can return -1.


// typeof grid === "object" (array of arrays of 1 character)

const getFood = (grid) => {

    const queue = []
    
    for(let row = 0; row < grid.length; row++) {
        for(let column = 0; column < grid[0].length; column++) {
            if(grid[row][column] === "*"){
                queue.push([row, column, 0])
                break
            }
        }
    }

    const directions = [[-1, 0], [0, -1], [1, 0], [0, 1]]
    const seen = new Set()
    
    while(queue.length) {
        let [row, column, distance] = queue.shift()
        if(grid[row][column] === "#") return distance
        const lookup =`${row}+${column}`
        if(seen.has(lookup) continue
        seen.add(lookup)
                
        for(let [x, y] of directions) {
            let newRow = x + row;
            let newColumn = y + column;
            
            if(newRow >= 0 && newRow < grid.length && newColumn >= 0 &&
            newColumn < grid[0].length && grid[newRow][newColumn] !== "X") {
                    queue.push([newRow, newColumn, distance + 1])
            }
        }
    }

    return -1

}

Given a list of edges (connected node pairs), does the collection make up a valid tree? In order to determine this, you can turn the edge list into an adjacencyList, and then run a depth-first search on the adjacencyList, starting from an arbitrary node. Add each node to a seen set as you visit it, and make sure not to backtrack as you proceed along a depth-first search. If ever a duplicate node is detected, return false, and if at the end of the traversal, the number of nodes seen is not equal to the number of nodes detected (indicateing split graphs) return false. Else return true.


// typeof n === "number"
// typeof edges === "object" (array of arrays with 2 numbers)

const validTree = (n, edges) => {

    const adjacencyList = [...Array(n)].map(() => [])
    const seen = new Set()

    for (let edge of edges){
        const [node1, node2] = edge
        adjacencyList[node1].push(node2)
        adjacencyList[node2].push(node1)
    }

    const dfs = (current, last) => {
        if(seen.has(current)) return false
        seen.add(current)

        for(let next of adjacencyList[current]){
            if(next === last) continue
            if(!dfs(next, current)) return false
        }
        return true
    }

    if (!dfs(0, -1)) return false

    return n === seen.size

}

How to finish all courses given an array of their prerequisites, if this is possible? You can first construct an adjacencyList using prerequisites as keys and all courses that are a direct requirement of that prerequisite pushed to an array that is the key value. As the adjacencyList is being constructed, build up an indegrees array the size of the number of nodes, increasing the indegree by 1 of any direct consequent course whenever a new prerequisite is detected. Then, take all courses with an indegree of 0 and add them to a queue. Process the queue in a breadth-first search manner, repeatedly shifting out the top course from the queue, subtracting one from the indegree of any course it points to, and then adding to the queue any courses that now have an indegree of zero (for a later step in the breadth-first search). Finally, push the queue course being processed to the result, and move on to the next course in the queue, if any. If at the end not all nodes are processed, you can return an empty array to indiate not all courses can be reached. Otherwise, return result.


// typeof numCourses === "number"
// typeof prerequisites === "object" (array of arrays of 2 numbers)

const findOrder = (numCourses, prerequisites) => {

  const adjacencyList = new Map()
  const indegrees = new Array(numCourses).fill(0)

  for (const [course, prerequisite] of prerequisites) {
    if (!adjacencyList.has(prerequisite)) adjacencyList.set(prerequisite, [])
    adjacencyList.get(prerequisite).push(course)
    indegrees[course]++
  }

  const queue = []
  for (let i = 0; i < indegrees.length; i++) {
    if (!indegrees[i]) queue.push(i)
  }

  const result = []
  while (queue.length) {
    const course = queue.shift()
    if (adjacencyList.has(course)) {
      for (const nextCourse of adjacencyList.get(course)) {
        indegrees[nextCourse]--
        if (!indegrees[nextCourse]) queue.push(nextCourse)
      }
    }
    result.push(course)
  }

  return numCourses === result.length ? result : []

}

To count the number of connected components in a undirected graph, one method involves converting connections or edges into an adjacencyList that maps out every connection between two nodes. Then every node on the adjacencyList can be iterated. If the node has been visited it can be passed over, but if it has not yet been visited, a result counter starting at 0 can increase by 1. Then, if the result counter incremented, perform a depth-first search starting at the iterated node and continuing to each node that can be reached from it through any number of adjacencyList connections that has not yet been visited. Mark each as visited and only stop (base case) on a node that has already been visited to prevent infinite loops. The depth-first search will allow only the first visit to a new connected component to add to the result tally.


// typeof n === "number"
// typeof edges === "object" (array of arrays with 2 numbers)

const countComponents = (n, edges) => {

  const adjacencyList = Array(n).fill().map(r => [])
  const visited = new Set()
  
  for (let [node1, node2] of edges) {
    adjacencyList[node1].push(node2)
    adjacencyList[node2].push(node1)
  }
  
  const dfs = (node) => {
    if (visited.has(node)) return
    visited.add(node)
    for (let nextNode of adjacencyList[node]) {
      dfs(nextNode)
    }
  }

  let result = 0

  for (let i = 0; i < n; i++) {
    if (visited.has(i)) continue
    result++
    dfs(i)
  }

  return result

}

To determine how many moves to takes to get from position 0,0 to position x,y on an infinite chess board, first you can choose to investigate absolute value of both x and y to take advantage of the problem’s symmetry. Then, create a queue seeded with the origin point, and perform breadth-first search with a nextQueue. For every item processed from the queue’s front, if it is at the desired end position, return a moves counter that starts at 0. Else, consider the eight directions a knight can move – changing x or y coordinates 1 space or 2 spaces, and then changing whichever coordinate remains whichever of 1 or 2 was not already chosen. If a position has already been seen or is below -2, it can be ignored, otherwise add the coordinates to the nextQueue. Once the queue is emptied, replace it with nextQueue, make nextQueue empty, and increase the moves counter by 1.


// typeof x === "number"
// typeof y === "number"

const minKnightMoves = (x, y) => {
    const absX = Math.abs(x)
    const absY = Math.abs(y)

    const directions = [[1,2],[2,1],[-1,-2],[-2,-1],[-1,2],[2,-1],[1,-2],[-2,1]]
    const seen = new Set()
    seen.add(`0-0`)

    let moves = 0
    let queue = [[0,0]]
    let nextQueue = []

    while(queue.length){
        const current = queue.shift()
        if(absX === current[0] && absY === current[1]) return distance
        for (let direction of directions){
            const newX = current[0] + direction[0]
            const newY = current[1] + direction[1]
            if(newX < -2 || newY < -2 || seen.has(`${newX}+${newY}`)){
              continue
            }
            seen.add(`${newX}+${newY}`)
            nextQueue.push([newX, newY])
        }
        if(!queue.length){
            moves++
            queue = nextQueue
            nextQueue = []
        }
    }

}

To find the cheapest cost between two cities within a certain number of intermediate stops and the help of an array providing flight sources, destinations, and costs, you can first note that you can immediately return 0 if the source city is the same as the destination city. Otherwise, you can create an adjacencyList storing a set of arrays of destination and cost pairs under each source city. You can then create a destinationPrices array with length equal to the number of cities, and start each value as Infinity. This creates conditions for work on a queue. Declare a queue array with an array containing the source location and a cost of 0, and declare an empty nextQueue array. While the queue has a length and the number of stops plus 2 is greater than 0 (this counts the source and destination), pop an item from the queue and check if the cost in destinationPrices for the location is greater than the cost that was popped. If so, update the cost in destinationPrices to the smaller number, and if the adjacencyList can look up the location, loop all neighbors the adjacencyList can find and push an array of two values to nextQueue. This small array includes the destination location, and then cost at the current location plus the destinationPrice as the second value in the array. Regardless of if destinationPrices count be updated, before checking if the queue has length again, and if the queue has no length, reduce the allowed number of stops by 1, update queue to nextQueue, and assign an empty array to nextQueue. When the outer loop finally ends, you can return -1 if the destinationPrices array still has the goal location value as Infinity. Otherwise, return whatever value is there.


// typeof n === "number"
// typeof flights === "object" (array of arrays of 3 numbers)
// typeof src === "number"
// typeof dst === "number"
// typeof k === "number"

const findCheapestPrice = (n, flights, src, dst, k) => {
    
    if (src === dst) return 0
    
    const adjacencyList = new Map()
    for (let [source, destination, cost] of flights) {
        const newSet = (adjacencyList.get(source) || new Set()).add([destination, cost])
        adjacencyList.set(source, newSet)
    }

    const destinationPrices = new Array(n).fill(Infinity)
    let queue = [[src, 0]]
    let nextQueue = []
        
    while(queue.length && k + 2 > 0) {
    const [location, cost] = queue.pop()
    
        if (destinationPrices[location] > cost) {
            destinationPrices[location] = cost

            if (adjacencyList.has(location)) {
                for (let neighbor of adjacencyList.get(location)) {
                    const [destination, destinationPrice] = neighbor
                    nextQueue.push([destination, cost+destinationPrice])
                }                
            }
        }

        if(!queue.length){
            k--
            queue = nextQueue
            nextQueue = []
        }

    }
    
    return isFinite(destinationPrices[dst]) ? destinationPrices[dst] : -1

}

To count how many steps it takes to transform one word into a different word of equal length, given a list of valid intermediate words, you can set a changes variable to 1, a queue array to contain the starting word, a visited set to contain the starting word, and a dictionary to contain all legal intermediates. While queue has a length, loop each word in the queue. For each, if any is the intended end word, return changes immediately. Else, for that word in the queue, test replacing all one-character-changes: every letter with every possible letter. If the new word is already in visited or is not in the dictionary you can ignore it, else push to a nextQueue. If every word in the queue finishes, replace the queue with nextQueue and start the next round. If the queue is empty without finding a solution you can return 0.


// typeof beginWord === "string"
// typeof endWord === "string"
// typeof wordList === "object" (array of strings)

const ladderLength = (beginWord, endWord, wordList) => {

  let changes = 1
  let queue = [beginWord]
  const visited = new Set(queue)
  const dictionary = new Set(wordList)
  
  while (queue.length) {
    const nextQueue = []
    for (let word of queue) {
      if (word === endWord) {
        return changes
      }
      
      const wordArrayForChanges = word.split('')
      for (let i = 0; i < wordArrayForChanges.length; i++) {
        for (let replacementLetter of "abcdefghijklmnopqrstuvwxyz") {
          wordArrayForChanges[i] = replacementLetter
          const newWord = wordArrayForChanges.join('')
          if (!visited.has(newWord) && dictionary.has(newWord)) {
            nextQueue.push(newWord)
            visited.add(newWord)
          }
          wordArrayForChanges[i] = word[i]
        }
      }
    }
    queue = nextQueue
    changes++
  }
  
  return 0
  
}

To find the longest increasing path in a matrix of numbers, first note that an empty matrix can return 0. Otherwise, you can set best to 1, then loop the input matrix. At every step, run a helper function to see if best can be updated. The helper function takes the location of the matrix iteration, returns a value in a cache if a value is present, and otherwise creates a localBest stating at 1 (because a series of 1 is technically increasing). Run the function recursively as many as four times, in the up, down, left, and right directions, as long as the given direction contains a value less than or greater than the current value (choose one direction of comparison for the whole function). Therefore, localBest four has chances to update with each helper return +1 depending on which direction gives the best result. At the end of the recusive calls, update the cache with the localBest and then return localBest. At the end of the original matrix traversal, return best.


// typeof matrix === "object" (array of arrays of numbers)

const longestIncreasingPath = (matrix) => {
  if (!matrix?.length) return 0

  const directions = [[-1, 0], [1, 0], [0, 1], [0, -1]]
  const cache = [...Array(matrix.length)].map(() => Array(matrix[0].length).fill(0))

  const go = (x, y) => {
    if (cache[x][y]) return cache[x][y]
    let localBest = 1
    for (const [dx, dy] of directions) {
      const i = x + dx
      const j = y + dy
      if (i >= 0 && i < matrix.length && j >= 0 && j < matrix[0].length && matrix[i][j] < matrix[x][y]) {
        localBest = Math.max(localBest, go(i, j) + 1)
      }
    }
    cache[x][y] = localBest
    return localBest
  };

  let best = 1
  for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix[0].length; j++) {
      best = Math.max(best, go(i, j))
    }
  }
  return best

}

In order to find which words from a list can be spelled through up/down/left/right adjacent characters on a grid of characters, you can begin by creating an empty results array. Then construct a trie out of the input array of words, but use the word itself as the stop sign so what word has been found will be able to be detected. Loop the board and at every location run a helper function with the location and a copy of the trie root. This function will run recusively, so if the trie node in the helper indicates reaching the end of a word with a stop sign, add that word to results and then remove the stop sign so the word does not get added twice. Otherwise, if the location is off the board or the letter at the location is not found at the current trie node return. If the function continues, save the boardCharacter at the location, temporarily override it with a nonletter character so it does not get examined again in recursive traversals, and then call the helper recursively in every legal direction with a copy of the trie moved down in the direction of the boardCharacter. At the end of the recursive calls, restore the letter at the location so it can be used in a traversal starting at a different location.


// typeof board === "object" (array of array of 1 character strings)
// typeof words === "object" (array of strings)

const findWords = (board, words) => {

  const buildTrie = () => {
    const root = {}
    for (const word of words) {
      let node = root
      for (const character of word) {
        if (!node[character]) node[character] = {};
        node = node[character]
      }
      node.word = word
    }
    return root
  }

  const results = []
  const trie = buildTrie()
  const directions = [[-1, 0], [1, 0], [0, 1], [0, -1]]

  const go = (node, x, y) => {
    if (node.word) {
      results.push(node.word)
      node.word = null
    }

    if (x < 0 || x >= board.length || y < 0 || 
    y >= board[0].length || !node[board[x][y]]) return

    const boardCharacter = board[x][y]
    board[x][y] = '#'
    for (const [dx, dy] of directions) {
      const i = x + dx
      const j = y + dy
      go(node[boardCharacter], i, j)
    }
    board[x][y] = boardCharacter
  }

  for (let i = 0; i < board.length; i++) {
    for (let j = 0; j < board[0].length; j++) {
      go(trie, i, j)
    }
  }
  return results

}

Given a list of words, how to return a an alphabetized list of their letters as if the words were in a sorted dictionary? You can start by creating an indegree map that sets every unique character in every word to have an indegree of 0 as a start. Then you can loop the input list of words, comparing every adjacent pair. At every adjacent pair, find the minimum length of the two words, and loop this number of characters, starting at the beginning of both words. Proceed until the first time these characters no longer match, then add the character of the prior word to a key in an adjacencyList with value of an empty set if this has not already been done. Then, in this set, if the letter in the second word cannot be found, add it to the first word’s letter’s set, and raise the indegree of the second word’s letter by 1 (as it has at least that many letters ahead of it in the dictionary). Since only one pair of letters can be compared per pair of words, next break the character loop. For the last phase of the algorithm, start a queue with all of the keys in the indegree map. Then, while the queue has a length, shift off the front item, push its value to a result array (the very first item by definition has no precursors), and reduce the indegree by 1 of every letter found when looking up the original letter in the adjacencyList. If one of these “next” letters has an indegree of 0 after this action, push it to the queue. When the queue is fully processed, the result can be returned.


// typeof words === "object" (array of words)

const alienOrder = (words) => {

  const indegree = new Map()
  for (let word of words) {
    for (let character of word) {
      if (!indegree.has(character)) {
        indegree.set(character, 0)
      }
    }
  }

  const adjacencyList = new Map()

  for (let i = 0; i < words.length - 1; i++) {
    const word1 = words[i]
    const word2 = words[i + 1]
    const minLength = Math.min(word1.length, word2.length)
    for (let j = 0; j < minLength; j++) {
      if (word1[j] !== word2[j]) {
        const character1 = word1[j]
        const character2 = word2[j]
        if (!adjacencyList.has(character1)) {
          adjacencyList.set(character1, new Set())
        }
        if (!adjacencyList.get(character1).has(character2)) {
          adjacencyList.get(character1).add(character2)
          indegree.set(character2, indegree.get(character2) + 1);
        }
        break
      }
    }
  }

  const result = []
  const queue = Array.from(indegree.keys()).filter(c => indegree.get(c) === 0)
  while (queue.length > 0) {
    const c = queue.shift()
    result.push(c)
    if (adjacencyList.has(c)) {
      for (const neighbor of adjacencyList.get(c)) {
        indegree.set(neighbor, indegree.get(neighbor) - 1)
        if (indegree.get(neighbor) === 0) {
          queue.push(neighbor)
        }
      }
    }
  }

  return result

}

To determine the minimum number of buses to take when given a number of looping bus routes with stops (each with a unique bus), if the source is not the same as the target (which allows immediate return of 0), you can create a new route data such that each bus stop points to an array with all possible buses that reach it. Then a queue array starting with only the source bus stop can be reviewed in breadth-first search style. At each step, the front busStop in the queue is shifted off, and all buses that reach that busStop are collected and iterated. If the bus has already been visited, continue (preventing loops), else add the bus to a visited set and loop all stops the bus reaches (using a location in the original routes input array). If the destination is found, return a count tally started at 1, else push the new stop number into a nextQueue. If after reviewing all buses that go to the busStop currently processed, there are no more stops in the queue, increment count by 1, replace queue with nextQueue, and replace nextQueue with an empty array. If queue is still exhausted, you can return -1 to indicate it is not possible to reach the target.


// typeof routes === "object" (array of arrays of numbers)
// typeof source === "number"
// typeof target === "number"

const numBusesToDestination = (routes, source, target) => {
    if (source === target) return 0

    const busStops = new Map()
    for (let i = 0; i < routes.length; i++) {
        for (let j = 0; j < routes[i].length; j++) {
            const busStop = routes[i][j]
            const buses = busStops.get(busStop) || []
            buses.push(i)
            busStops.set(busStop, buses)
        }
    }
    
    const visited = new Set()
    let count = 1
    let queue = [source]
    let nextQueue = []
    while (queue.length) {
        const busStop = queue.shift()
        const buses = busStops.get(busStop)
        for (let bus of buses) {
            if (visited.has(bus)) continue
            visited.add(bus)
            for (let j = 0; j < routes[bus].length; j++) {
                const nextBusStop = routes[bus][j]
                if (nextBusStop === target) return count
                nextQueue.push(nextBusStop)
            }
        }
        if (!queue.length){
            count++
            queue = nextQueue
            nextQueue = []
        }
    }
    
    return -1

}

Binary Search Trees

Binary search trees are binary trees that have the property that every node bisects the search space – you can tell which side of a node to go down for further investigation based on its value. In order to find the lowest common ancestor of two nodes, consider that as you decend the tree, as long as the current value is less than the lower value of the two target nodes, or greater than the upper value of the two target nodes, both target nodes will be on the same side of the next step down of the tree. Decend recursively in the direction of both nodes until you no longer can, and return the stopping value.


// typeof root === "object" (binary search tree node)
// typeof p === "object" (binary search tree node)
// typeof q === "object" (binary search tree node)

const lowestCommonAncestor = (root, p, q) => {

    const large = Math.max(p.val,q.val)
    const small = Math.min(p.val,q.val)

    while(root.val > large || root.val < small){
        if(root.val > large) root = root.left
        else if (root.val < small) root = root.right

    }

    return root

}

Converting an array of numbers in ascending order into a height-balanced binary search tree can be handled by bisection and recursion. The height of the tree never has to be explictly measured. As long as there is at least one value in an examined range of the array, the central node of the segment of the array can become the head node of the segment of the binary search tree, and all numbers lower in the segment can be the subject of a recursive call to fill the .left node, while all numbers higher can fill .right.


// typeof nums === "object" (array of numbers)

const sortedArrayToBST = (nums) => {
    return recursion(0, nums.length - 1, nums)
}

const recursion = (left, right, nums) => {

    if(left > right) return null

    const node = new TreeNode()
    const mid = Math.floor((left+right)/2)

    node.val = nums[mid]
    node.left = recursion(left, mid - 1, nums)
    node.right = recursion(mid + 1, right, nums)

    return node

}

To check if a binary tree is a valid binary search tree, recursion is valuable. Set left and right default parameters of -Infinity and Infinity on the function, and run it so that an empty node returns true, while otherwise two recursive calls are spawned. One takes the .left node with a left of left and a right of root.val, while the other takes the .right node with a right of right and a left of root.val. Then return true only if the left node and the right node return true, and root.val is between left and right.


//typeof root === "object" (binary search tree node)

const isValidBST = (root, left = -Infinity, right = Infinity) => {
    if (!root) return true
    return (isValidBST(root.left, left, root.val)
    && isValidBST(root.right,root.val, right)
    && left < root.val && root.val < right)
    
}

To find the Kth smallest element in a binary search tree, you can search the tree with an inorder depth-first search from left to right, and find the Kth value. Every time you reach a value, decrease by 1 a counter equal to k, and then, at the location where you decremented, check if k has reached 0. If so, return the value at the node, and use a nonallowed value to return otherwise, making sure that only the correct value bubbles up to the final return statement.


// typeof root === "object" (binary search tree node)
// typeof k === "number"

const kthSmallest = (root, k) => {
    const dfs = (node) => {
        if(!node) return -1

        const leftResult = dfs(node.left)
        if (leftResult > -1) return leftResult

        if (--k === 0) return node.val

        const rightResult = dfs(node.right)
        if (rightResult > -1) return rightResult

        return -1
    }
    return dfs(root)

}

To find the inorder successor of a node in a binary search tree, there are two basic cases. First, if the node for which the successor is being searched has a .right, the higher parts of the tree do not need to be considered, and the result is one .right followed by as many .left as are available. Otherwise, you can declare a successor variable and set it to null, then navigate through nodes starting at the root as long as the active node is not the node for which the successor is being considered. To do this, at every step in this section option for the algorithm, if the value at the current node is greater than the node for which the successor is trying to be found, set successor to the active node and then the active node to the .left. Otherwise, if a .right is available, you can go to the .right without moving the successor (because doing that would put the successor behind the node searched for, not ahead of it). In a case where there is no further .right, but the value is still less, not only do you not have to move the successor, but you can also break.


// typeof root === "object" (binary search tree node)
// typeof p === "object" (binary search tree node)

const inorderSuccessor = (root, p) => {

    if(p.right){
        p = p.right
        while(p.left) p = p.left
        return p
    }

    const successor = null

    while(root !== p){
        if (root.val > p.val){
            successor = root
            root = root.left
        } else if (root.right){
            root = root.right
        } else {
            break
        }
    }

    return successor

}

Hash Tables

Whether one group of characters in a string is completely included in a different string can be described as being able to cut letters out of a magazine for a ransom note. If the only characters that can appear are lower case letters, then these can be added by character code into an array from the “magazine” string, and then what appears in the “note” string can be subtracted. If any count in the array goes negative, the ransom note cannot be completed.


// typeof ransomNote === "string"
// typeof magazine === "string"

const canConstruct = function(ransomNote, magazine) {

    const arr = new Array(26).fill(0)

    for (let i = 0; i < magazine.length; i++){
        arr[magazine.charCodeAt(i)-97]++
    }

    for (let i = 0; i < ransomNote.length; i++){
        arr[ransomNote.charCodeAt(i)-97]--
        if(arr[ransomNote.charCodeAt(i)-97] < 0) return false
    }

    return true

}

To create a data struture such that elements can be inserted (returning a boolean, true if the element is not already present), removed (returning a boolean, true, if the element is already present), or randomly selected with equal probability, a function set to a variable can instantiate an empty array and an empty object, both set under this. To insert, at a prototype method, if the input is already found by direct lookup in the object, return false. Else, push the input to the array, note its index, and set a key-value pair in the object such that the key is the input being set and the value is the index in the array. Then return true. To remove, at a prototype method, if the input cannot be found by object lookup, return false. Else, pop the last item in the array, then used the popped item as a key lookup in the object and replace its index value with the index value of the input to be removed. Next, delete the index lookup in the object of the input being removed, but use its former index location, already stored in the object under the item being moved, to assign the last item to that location. Then return true. Finally, to get a random value on the prototype, floor a random value to the length of the array and return whatever item is at that index.



const RandomizedSet = function() {
    this.array = []
    this.object = {}
}

RandomizedSet.prototype.insert = function(val) {
    if(this.object[val] !== undefined) return false
    this.array.push(val)
    const index = this.array.length - 1
    this.object[val] = index
    return true
}
RandomizedSet.prototype.remove = function(val) {
    if(this.object[val] === undefined) return false
    const last = this.array.pop()
    this.object[last] = this.object[val]
    delete this.object[val]
    this.array[this.object[last]] = last
    return true
}

RandomizedSet.prototype.getRandom = function() {
    const index = Math.floor(Math.random()*this.array.length)
    return this.array[index]
}

How to find the smallest missing positive integer from an array in linear time? You can solve this problem with two array traversals. In the first traversal, while the value being reviewed is positive, the value is less than or equal to the length of the array, and the index location equal to the value minus 1 (so the 0th index would contain 1) does not already contain the value, swap the value at the current location with the value at the index location equal to the value minus 1. What this does is repeatedly place values in the “correct” location from the value at the highlighted index, bring the values that had been at the “correct” location to the highlighted index, and continue as long as this process is productive. After this loop is attempted for every array index, loop the array again and return the index plus 1 the first time an index does not contain itself plus 1. If this loop is finished without returning anything you can return the length of the array plus 1 because the first missing number would then be just greater than the length of the array.


// typeof nums === "object" (array of numbers)

const firstMissingPositive = (nums) => {

    for (let i = 0; i < nums.length; i++){
        while(nums[i] > 0 && nums[i] <= nums.length && nums[nums[i] - 1] !== nums[i]){
            [nums[nums[i] - 1], nums[i]] = [nums[i], nums[nums[i] - 1]]
        }
    }
    
    for (i = 0; i < nums.length; i++) {
        if (nums[i] !== i + 1) return i + 1
    }
    return nums.length + 1

}

Dynamic Programming

If someone can climb one or two steps at a time, how many ways can they get to the top? Dynamic programming helps recognize that the number of ways to reach any given step acts as a multiplier on the ways to reach steps further up. A linear solution involves noticing the one way of getting to the 0th step, adding the number of ways to get to the 0th step to the 1st and 2nd steps, then “stepping” from the 1st step to the 2nd and 3rd steps, and adding counts so on and so on. Only three locations need to be available at a time.


// typeof n === "number"

const climbStairs = (n) => {

    let steps = [1,0,0]
    let count = 0

    while(count < n){
        let next0 = steps[1] + steps[0]
        let next1 = steps[0]
        
        steps = [next0, next1, 0]
        count++
    }

    return steps[0]

}

To find the maximum sum of a subarray (consecutive numbers) in an array with a length, you can initialize a best variable and a currentMax variable both to the value of the first number in the array. Then, looping the rest of the numbers, at every step you can first update currentMax to be the better of currentMax plus the pointed value, or just the pointed value (this will remove negative values of currentMax). As the second part of each array step, update best to be currentMax if currentMax is ever the greater of the two. At the end of the loop, return best. This is a version of Kadane’s algorithm .


// typeof nums === "object" (array of numbers)

const maxSubArray = (nums) => {

    let best = nums[0]
    let currentMax = nums[0]

    for (let i = 1; i < nums.length; i++){
        currentMax = Math.max(currentMax+nums[i], nums[i])
        best = Math.max(best,currentMax)
    }

    return best

}

To find the fewest number of coins that sum to a value, with an infinite amount of coins but specific coin types, a dp array can be used with length equal to 1 + amount of the goal value. In this dynamic programming approach, seed the 0th location in dp with 0 to indicate it takes 0 coins (the value) to reach a partial amount of 0 (the index). Then, loop through the dp array for each available coin, and when a given value is noninfinite, update the value at the location a coin amount up from the index to be the minimum of itself or the source location plus 1. Each new coin can fill in new intervals from each available starting index. At the end, you can return a finite value at the last location in dp, or -1.


// typeof coins === "object" (array of numbers)
// typeof amount === "number"

const coinChange = (coins, amount) => {

    const dp = new Array(amount+1).fill(Infinity)
    dp[0] = 0

    for (let coin of coins){
        for (let i = 0; i < dp.length-coin; i++){
            dp[i+coin] = Math.min(dp[i+coin], dp[i]+1)
        }
    }

    if(isFinite(dp[amount])) return dp[amount]
    return -1

}

Can a non-empty array containing only positive integers be split into two subsets that are equal? To determine, you can collect the total sum and immediately retrun false if that sum is an odd number. Else, you can use dynamic programming to see if there is some combination of numbers that sums to half the total sum. Make a dp array equal to one more than the sum, seed the 0 location (representing a sum of 0) with true, and for each number in the original array, loop the dp array from right to left, checking if the current value location minus the value being considered is true, and if so, updating the current value location to true. Looping from left to right would reuse numbers. At the end, return the boolean status of the right end of the array.


// typeof nums === "object" (array of numbers)

canPartition = (nums) => {
    
    const sum = nums.reduce((a,b)=>a+b)

    if(sum % 2) return false
    const dp = new Array(sum/2 + 1).fill(false)
    dp[0] = true
    
    for(let num of nums) {
        for(let i = sum; i >= num; i--){
            dp[i] = dp[i] || dp[i - num]
        }
    }
    
    return dp[sum/2]

}

How many ways are there for a robot in an array, that can only go to the right and down, to get from the upper-left to the lower-right? This problem can be solved with the help of a dp array. Create it as the length of a row, n, and fill all locations with 0, but update the 0th index with 1, to show there is 1 way for the robot to get to (be in) the upper-left. Then, loop for the number of rows m, and in each of those loops, create a nextDP array and loop each location in the row. For each location in the inner loop, update that location in nextDP as the sum of any values in locations just above (just above is represented by the dp array) and just to the left. At the end of each inner loop, replace dp with nextDP. Return the value at the rightmost index in dp at the end of the outer loop. Note that for the first inner loop, dp represents a theoretical m-1 row that allows the 0th location in the 0th row to be seeded correctly.


// typeof m === "number"
// typeof n === "number"

const uniquePaths = (m, n) => {

    let dp = new Array(n).fill(0)
    dp[0] = 1

    for (let i = 0; i < m; i++){
        let nextDP = []
        for (let j = 0; j < n; j++){
            let up = dp[j]
            let left = j ? nextDP[j-1] : 0
            nextDP[j] = up+left
        }
        dp = nextDP
    }

    return dp[dp.length-1]

}

To find the best maximum sum of nonadjacent values in an array (which in LeetCode is themed stealing from nonadjacent houses), consider a linear approach that uses two helper variables, a back1Best and a back2Best. Initialize both to 0, and iterate through the array from left to right. At each step, save a copy of back1Best, update back1Best to be the max of itself or the current value plus back2Best, then update back2Best to be the saved copy of the old back1Best. At the end of the array iteration, back1Best will contain the maximum sum.


typeof nums === "object" (array of numbers)

const rob = (nums) => {

    let back1best = 0
    let back2best = 0

    for (let house of nums){
        const temp = back1best
        back1best = Math.max(house + back2best, back1best)
        back2best = temp
    }
    return back1best

}

To find the maximum product in any subarray (contiguous series) of an array, you can use three helpers, a minimum, a maximum, and a best. Intialize each to the first value, then loop the remainder, at every step updating the minimum and maximum to be the new maximum or minimum of the current value in the loop, the value indicated in the loop times the maximum, or the value indicated in the loop times the minimum. Then update best. The minimum is saved because negative numbers multiplied turn positive.


// typeof nums === "object" (array of numbers)

const maxProduct = (nums) => {
    
    let maximum = nums[0]
    let minimum = nums[0]
    let best = nums[0]
    
    for(let i = 1; i < nums.length; i++){
        const maximumCopy = maximum
        maximum = Math.max(nums[i], maximum*nums[i], minimum*nums[i])
        minimum = Math.min(nums[i], maximumCopy*nums[i], minimum*nums[i])
        best = Math.max(best, maximum)
    }
    return best

}

To find the longest strictly increasing subsequence in an array (numbers have to be in order, but do not need to be adjacent), binary search can help insert the lowestValueReachingCount into a helper array. To do this, for every number in the input array, declare a left of 0 and a right equal to the current best length (starting at 0). Then, while left is not right create a while loop, generating a mid between left and right at every round. If the value at index mid in lowestValueReachingCount is less than the number being investigated in the outer loop, this means the longest strictly increasing subsequence ending at the original array number being investigated is at least equal to mid+1, so set the new left to mid+1 and continue. Else, right can be mid. When the left and right pointers meet, update the location in lowestValueReachingCount with the number indicated in the outer loop (because if it were not the lowestValueReachingCount, the mid+1 process would have moved the index up), and update best with the location index found in the binary search, plus 1, if appropriate.


// typeof nums === "object" (array of numbers)

const lengthOfLIS = (nums) => {

    const lowestValueReachingCount = new Array(nums.length).fill(0)
    let best = 0
    
    for (let num of nums){
        let left = 0
        let right = best
        while(left !== right){
            const mid = left + Math.floor((right-left)/2)
            if (lowestValueReachingCount[mid] < num) left = mid + 1
            else right = mid
        }
        lowestValueReachingCount[right] = num
        best = Math.max(best,right+1)
    }
    
    return best

}

Given a starting position at the 0th index of an array, if the value at any position indicates the maximum range one can “hop” further to the right, can any series of hops reach the final value? In order to solve, you can linearly traverse the array starting at the 1st index, keeping track of a maximum range, which starts at the value at the 0th index. If at any point the index of the traversal is larger than the index given by the range, return false, otherwise update the range to be the best of itself or the index plus the value at the index. If the entire array loop is completed without returning false, you can return true.


// typeof nums === "object" (array of numbers)

const canJump = (nums) => {

    let range = nums[0]
    
    for(let i = 1; i < nums.length; i++){
        if(range < i) return false

        range = Math.max(range, i + nums[i])
    }
    return true

}

To find the maximum area of a square all of “1” inside a 2D input array, you can create a 2D array for dynamic programming with length and width both equal to that of the input array plus 1. Fill all locations with 0. Then loop through the input matrix, and every time you find a “1”, replace the equivalent location in the dynamic programming array (which should be 1 ahead in both the x and y dimensions) with the minimum of its left, upper, and upper-left location plus 1. This is a candidate for the best square, so update a best variable with the value assigned to the dynamic programming array location, if appropriate. At the end of reviewing the input matrix, return the square of best.


// typeof matrix === "object" (array of arrays of numbers)

const maximalSquare = (matrix) => {
    const dp = [...Array(matrix.length+1)].map(_ => [...Array(matrix[0].length+1)].fill(0))
    
    let best = 0
    for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[0].length; j++) {
            if (matrix[i][j] === "1") {
                const last = Math.min(dp[i][j], dp[i][j+1], dp[i+1][j])
                dp[i+1][j+1] = last + 1
                best = Math.max(best, dp[i+1][j+1])
            }
        }
    }
    
    return best ** 2

}

To determine the number of ways a string of numbers can be decoded as letters, where “A” is “1” and so on until “Z” is “26”, you can notice that if the string has no length or the first digit is “0”, there are 0 possible mappings. Otherwise, you can create a small dp array with length 2, and both digits initially filled with 1. The first 1 gives a hypothetical number of ways for the -1 digit of the string to be decoded (useful for a later loop), while the second 1 gives the number of ways for the 0 index digit of the string to be decoded (since we know it is not 0). Next, loop the string, starting at the 1st index. At every step, create a next variable starting at 0, and add the value of the 0 index of the dp array to next if the last and current indicies of the input array together range from “10” to “26”. Because only a single 2-digit number is possible, this means you can add to next the value/number of combinations from two indicies ago (this is how the hypothetical 1 makes sense). Then, if the current index of the input array is not “0”, you can add to next the value/number of combinations from one index ago – “0” does not add anything because it is the only digit that must combine with the previous digit to count, so does not add any possibilities that rely on the previous digit being an ending digit. At the end of each step of the loop, change the 0 index of the dp array to the value in the 1 index, and then use next to update the 1 index. At the end of the loop, return the value in the 1 index.


// typeof s === "string"

const numDecodings =(s)=> {

  if (!s.length || s[0] === "0") return 0

  const dp = [1, 1]
    
  for (let i = 1; i < s.length; i++) {
      let next = 0
    if (s[i-1] === '1' || s[i-1] === '2' && s[i] <= '6') {
      next += dp[0]
    }
    if (s[i] !== '0') {
      next += dp[1]
    }
    dp[0] = dp[1]
    dp[1] = next
  }

  return dp[1]

}

To count the number of ways to sum to a target in an array of unique integers, you can create a dp array, sort the input array from least to greatest, and fill the dp array with a single count at every location equal to a value in the input array. Then, you can use dynamic programming to loop to every integer from 1 to the target value. At each, declare a count variable started at 0. Then, for each integer being targeted, loop the sorted input array until the loop runs out or the value at the input array location meets or exceeds the integer being targeted in the outer loop. For each of these inner loop steps, add to the count any value in dp at an index equal to the target integer less the value indicated at the inner loop. At the end of the inner loop, set dp at the index pointed to by the outer array equal to its current value, if any, plus count. At the end of the looping, return the value at the location of the target in dp.


// typeof nums === "object" (array of numbers)
// typeof target === "number"

const combinationSum4 = (nums, target) => {

    const dp = []
    nums.sort((a,b) => a-b)
    for (let num of nums){
        dp[num] = 1
    }
    for(let i = 1; i <= target; i++){
        let count = 0
        for(let j = 0; j < nums.length && nums[j] < i; j++){
            count += dp[i - nums[j]]
        }
        dp[i] = dp[i] + count || count
    }
    return dp[target]

}

Binary

To sum two binary strings, set indices to point to the rightmost (lowest) element in both. Set a carry variable to 0. Loop until both indices have reached the leftmost end of their strings. At each step in the loop, add the numerical sum of valid index values to carry (ignoring any index that is less than 0). Next, and also in each loop, push carry%2 to a result array, and reset carry to its own base 2 overflow. Once both strings have been looped, add any final value in carry to the result array, if a final value exists, then reverse the array and join it into the result string.


// typeof a === "string"
// typeof b === "string"

const addBinary = (a, b) => {
    const result = []
    
    let aIndex = a.length-1
    let bIndex = b.length-1
    let carry = 0

    while(aIndex !== -1 || bIndex !== -1){
        if(bIndex > -1){
            carry += Number(b[bIndex])
            bIndex--
        }
        if (aIndex > -1){
            carry += Number(a[aIndex])
            aIndex--
        }

        result.push(carry%2)
        carry = Math.floor(carry/2)
    }

    if (carry) result.push(1)
    
    return result.reverse().join('')

}

To count the number of 1 in a binary representation of numbers from 0 to some arbitrary number, seed a results array with a 0 (there are 0 of 1 digits in 0). Then loop upwards as far as needed, seeding each next value in the results array with the value stored at half of it, rounded down, plus 1 if the leftmost digit in the new number is filled. Why look at the location half of it, rounded down? Because that is the equivalent of a bitshift right, so we only need to worry afterwards about one additional digit.


// typeof n === "number"

const countBits = (n) => {

    const results = [0]

    for (let i = 1; i <= n; i++){
        results[i] = results[i >> 1] + (i & 1)
    } 

    return results
       
}

A string’s Hamming weight is the number of its nonzeros. In binary, Hamming weight is all of the 1s. From the target number, we can run a loop that increments a counter at every step, and also replaces the original number with the bitwise AND of itself and itself - 1. This guarantees that the number of loop steps while there is a number is the Hamming weight. Why minus 1 on an odd number removes 1 nonzero should be self-explanatory (the bitwise AND here does nothing), while the bitwise AND with an even original interacts with the minus 1 to create the same effect because the subtraction will cascade to be pullled from the lowest avaiable 1 on the original, and all 1s created by the process will be wiped out by the bitwise AND.


// typeof n === "number"

const hammingWeight = (n) => {

    let weight = 0

    while(n){
        n &= n-1
        weight++
    }

    return weight

}

If in an array of numbers all numbers appear twice except a single number that appears once, a bitwise XOR trick can detect the lonely number in linear time. By XORing the entire series and returning the result, because XOR will return 0 on each digit for each matched digits, even identical numbers that are not adjacent will eventually cancel each other out.


// typeof nums === "object" (array of numbers)

const singleNumber = (nums) => nums.reduce((a,b)=>a^b)

To find a missing number in an array of numbers that would otherwise include all values between 0 and a given n, XOR all values in the array together with all values from 0 to the n. With the XOR, all doubled values should cancel out, leaving the missing value.


// typeof nums === "object" (array of numbers)

const missingNumber = (nums) => {

    let result = nums.length

    for(let i = 0; i < nums.length; i++){
        result ^= i ^ nums[i]
    }

    return result

}

In a 32-bit unsigned integer, what is the number represented by the reversal of the bits? In order to solve, start a result variable at 0, then loop all 32 digits of the binary version of the number to be reversed. At each step, double the result variable (a bitshift up by 1, but doubling with * in JavaScript as opposed to a direct bitshift prevents negative overflow). Then fill the new ones place on result with the presence or absence of the lowest digit remaining on the original number. Finally, for each step, bitshift down the number being read by 1.


// typeof n === "number"

const reverseBits = (n) => {

  let result = 0
  let count = 32

  while (count--) {
    result *= 2
    result += n & 1
    n >>= 1
  }

  return result

}

To find the single duplicate number in an array of otherwise consecutive numbers starting at 1, you can use a fast and a slow pointer. Assign slow to start as the value of the 0th index (which itself is an index in the array), and fast to the value at the index equal to the value at the 0th index. Then, continue updating fast and slow at this pace until fast is equal to slow. This means fast has caught up to slow in the circular pattern. To figure out what number is repeated, or, in other words, what number is indicated from two other indicies, restart one of the pointers at 0 and move each pointer to the value indicated at the index in tandem until they point to the same number. This is the result. This math works because to get to the first match, the slow pointer had to travel to the location indicated by two values, then some further distance, while the fast pointer had to travel both of these, plus a distance equal to the distance from the start to the location indicated by two values, plus the “further” distance again. (The fast must travel twice as far as slow.) Restarting one pointer to the 0th index and then moving them both forward step by step until they match means that both will meet the location indicated by two values (because both pointers are at the start of the phase that will end at the desired value).


// typeof nums === "object" (array of numbers)

const findDuplicate = (nums) => {

    let fast = nums[nums[0]]
    let slow = nums[0]
    while (fast !== slow){
        fast = nums[nums[fast]]
        slow = nums[slow]
    }
     
    fast = 0
    while (fast !== slow) {
        fast = nums[fast]
        slow = nums[slow]
    }
    
    return fast

}

Math

One way of converting a Roman numeral to a number involves creating a lookup object that links Roman numeral keys to their numerical values. A separate lookup object indicates which Roman numeral keys can have negative values by coming just before specific Roman numerals of greater value. A sum variable is created, and then, in a loop reading the full Roman numeral from left to right, the numeral at the index is read into the sum variable as if it is positive. If the next index is filled and contains a valid greater numeral, add twice the negative of the numeral at the current index to sum to help reach the final true value.


// typeof s === "string"

const romanToInt = (s) => {

    const look = {
        'I': 1,
        'V': 5,
        'X': 10,
        'L': 50,
        'C': 100,
        'D': 500,
        'M': 1000
    }

    const negs = {
        'I': new Set(['V','X']),
        'X': new Set(['L','C']),
        'C': new Set(['D','M'])
    }

    let sum = 0

    for (let i = 0; i < s.length; i++){
        sum += look[s[i]]

        if(negs[s[i]]?.has(s[i+1])){
           sum -= 2 * look[s[i]] 
        }

    }

    return sum

}

If a number is a palindrome, the reverse of the number is the same as the number. To reverse a number, create a reverse number set to 0, then loop through each digit of the original number. To do this, at every step, multiply the reverse by 10, and then add the lowest remaining digit of the original number to reverse. A way of finding the lowest remaining digit is by creating a copy of the original number, checking the ones place, and then dividing by 10 (rounding down) until the number is gone.


// typeof x === "number"

const isPalindrome = (x) => {

  if (x < 0) return false

  let reverse = 0
  for(let i = x; i > 0; i = Math.floor(i/10)){
      reverse = reverse * 10 + i%10
  }

  return reverse === x

}

To pick a random index from an input array, weighted by the integer values in the indicies (indicies with greater values have proportionally more share), you can save a pair of variables to this: the total of input array, and a runningTotals array of equal length to the input array. Fill each index of runningTotals with the sum of every value from that position to the left in the input array. In the selection method, generate a random integer from 0 up to but not including the total, then conduct binary search, with a floored mid index, moving the left pointer to mid+1 each time the random value is greater than the value at mid. Else you can move right to mid.


// typeof w === "object" (array of numbers)

const Solution = function(w) {
    this.total = 0
    this.runningTotals = []
    for(let value of w) {
        this.total += value
        this.runningTotals.push(this.total)
    }
}

Solution.prototype.pickIndex = function() {
    const random = Math.random() * this.total
    let left = 0
    let right = this.runningTotals.length-1
    while(left < right){
        const mid = left + Math.floor((right-left)/2)
        if(random > this.runningTotals[mid]){
            left = mid + 1
        } else {
            right = mid
        }
    }
    return left
}

To write a function that raises a number to a power, you can use a recursive algorithm. Notice that any power of 0 returns a 1. Declare a temporary result as 1 and and an absPower as the absolute value of the power. If absPower is odd you can multiply the result by the number being raised, and reduce the absPower by 1. Then multiply result by a recusive call of the function that squares the number being raised and uses half the absPower as the power. Finally, if the original power was negative, return 1 over the result, else return the result.


// typeof x === "number"
// typeof n === "number"

const myPow = (x, n) => {

    if (n===0) return 1
    
    let result = 1
    let absPower = Math.abs(n)

    if(absPower%2){
        result *= x
        absPower--
    }

    result *= myPow(x**2, absPower/2)
    
    if (n < 0) return 1/result
    return result

}

To reverse an integer as if it is a signed 32-bit value, you can start a reverse variable at 0. Declare a clamp at 2 to the 31 power. Then, while the input value still exists, multiply reverse by 10, add the value of the 0s place of the input value to reverse, and check if the reverse value is less than negative clamp or greater than clamp-1. Finally, divide the input value by 10 and remove any decimal. Once the input value no longer exists, return reverse.


// typeof x === "number"

const reverse = (x) => {

  let reverse = 0
  const clamp = Math.pow(2, 31)

  while (x) {
    reverse *= 10
    reverse += x % 10
    if ( reverse < -clamp || reverse > clamp - 1) return 0
    if (x > 0) x = Math.floor(x/10)
    else x = Math.ceil(x/10)
  }

  return reverse

}

Heaps

To find the k closest points to the origin, sort the points by the sum of the x distance squared and the y distance squared from the origin. No need to take square root because relative order will be the same either way. Return the k closest points. This algorithm can be done with a priority queue (which could always keep the lowest point on top based on its distance) but this is not a native data struture in JavaScript.


// typeof points === "object" (array of arrays with 2 numbers)
// typeof k === "number"

const kClosest=(points,k)=>points.map(p=>[...p,p[0]**2+p[1]**2]).sort((a,b)=>a[2]-b[2]).slice(0,k).map(p=>[p[0],p[1]])

If there are some number of tasks, assigned letters that can be duplicates, how long will it take to complete all tasks if tasks assigned the same letter must be separated by some cooldown number of spaces? To solve, you can fill a letterCounts array, sort this from greatest to least, and note the countOfMaximumSizeTypes (in other words, how many letters are in first place for maximum count). The remainder of the algorithm is just math. Note that the count of the most commmon letter minus 1 represents the number of full cooldown rounds that must take place, where the size of a cooldown round is cooldown+1 (because the item being cooled down must be included. Added to the product, because the last instance of the most common letter must also be included in the time, is simply the countOfMaximumSizeTypes, which represents a final partial cooldown round. However, if the number of tasks is greater than the product plus sum, simply return the number of tasks.


// typeof tasks === "object" (array of 1 character strings)
// typeof cooldown === "number"

const leastInterval = (tasks, cooldown) => {

  const letterCounts = new Array(26).fill(0)
  for (let task of tasks) {
    letterCounts[task.charCodeAt(0) - 65]++
  }
  letterCounts.sort((a,b)=>b-a)
  const countOfMaximumSizeTypes = letterCounts.filter((value ,index, array)=>value===array[0]).length

  return Math.max((letterCounts[0] - 1) * (cooldown + 1) + countOfMaximumSizeTypes, tasks.length)
}

To find some number of most frequent strings in an array, sorted by frequency from most common and then by lexicographical order, you can start by creating a counts object that stores the count of a given word. Then you can sort the unique words first by their count from greatest to smallest, and then as a tiebreaker, by their lexicographical order (the default sort). Then return however many from the front of this list is desired.


// typeof words === "object" (array of strings)
// typeof k === "number"

const topKFrequent = (words, k) => {
    
    const counts = {}
    for (let word of words) {
        counts[word] = counts[word] + 1 || 1
    }

    return Object.keys(counts).sort((a,b) => { 
        return counts[b] === counts[a] ? ( a > b || -1 ) : counts[b] - counts[a]
    }).slice(0, k)

}

To find the k closest elements to a given value in a sorted array of numbers, with the smaller range being chosen for a tiebreaker, one solution method is binary search. Create left and right index pointers with left being k-1 and right being the rightmost index of the array. Then, while left is less than right, create a floored mid, and check if the target value minus the value at mid-k+1 is greater than the value at mid+1 minus the target value. Because we are only interested in k values, not k, and given that the values are sorted, the result must be adjacent numbers, one of the two compared numbers must not be included in the result. We can set left to mid+1 if the test passes because that means the value at the low end has a greater “delta” with the target value, else we can set right to mid, which meets the condition of the tiebreaker. When the binary search resolves, return the k points ending at mid.


// typeof arr === "object" (array of numbers)
// typeof k === "number"
// typeof x === "number"

const findClosestElements = (arr, k, x) => {
    
  let left = k - 1
  let right = arr.length - 1

  while (left < right) {
    let mid = left + Math.floor((right-left)/2)
    if (x - arr[mid-k+1] > arr[mid+1] - x) {
      left = mid + 1
    } else {
      right = mid
    }
  }
  return arr.slice(left - k + 1, left + 1)

}

At this point it is worth reviewing heap syntax. The MaxHeap below can be used in future problems. Its constructor sets an empty array as this.heap. Other simple component methods include isEmpty, which checks if this.heap is empty, length, which checks the length of the heap, peekMax, which returns the top item in the heap if available, and swap, meant as a helper to swap two values in this.heap. Two more methods that are also simple, but are critical, and sit atop more complciate methods, are insert and extractMax. The insert method pushes a value to this.heap and then uses a siftUp method called with the final location in the array to place the new value correctly. The extractMax method notes that any first value in the heap is the maximum value and saves it, and to continue extraction, pops the last value in the heap, overwrites it to the earliest location in the heap if the length is greater than 1, uses a siftDown method to place this override value back in its proper location, and then returns the saved maximum value. The siftUp method runs a loop such that while the input index is greater than 0, the floor is repeatedly found of half of index-1, and if the value at the original index is greater than the value at this parentIndex, swap is called and the loop moves to point the index at the parentIndex. Otherwise the method can stop. The siftDown method is more complicated but follows a similar process – in a loop, a leftIndex and a rightIndex are repeatedly set at the doubled initial index plus 1 and the doubled initial index plus 2, respectively. A maxIndex is found among values in leftIndex if it is valid in the heap, rightIndex if it is valid in the heap, and the initial index. If the largest value is at the initial index, the loop breaks, otherwise a swap takes place between the maxIndex and the value at the top.



class MaxHeap {
  constructor() {
    this.heap = []
  }

  insert(val) {
    this.heap.push(val)
    this.siftUp(this.heap.length - 1)
  }

  extractMax() {
    if (this.isEmpty()) return null
    const max = this.heap[0]
    if (this.heap.length > 1){
        this.heap[0] = this.heap.pop()
        this.siftDown(0)
    } else {
        this.heap.pop()
    }

    return max
  }

  peekMax() {
    return this.isEmpty() ? null : this.heap[0]
  }

  isEmpty() {
    return this.heap.length === 0
  }

  siftUp(index) {
    while (index > 0) {
      const parentIndex = Math.floor((index - 1) / 2);
      if (this.heap[index] > this.heap[parentIndex]) {
        this.swap(index, parentIndex)
        index = parentIndex
      } else {
        break
      }
    }
  }

  siftDown(index) {
    while (true) {
      const leftIndex = index * 2 + 1
      const rightIndex = index * 2 + 2
      let maxIndex = index
      if (leftIndex < this.heap.length && this.heap[leftIndex] > this.heap[maxIndex]) {
        maxIndex = leftIndex
      }
      if (rightIndex < this.heap.length && this.heap[rightIndex] > this.heap[maxIndex]) {
        maxIndex = rightIndex
      }
      if (maxIndex !== index) {
        this.swap(index, maxIndex)
        index = maxIndex
      } else {
        break
      }
    }
  }

  swap(i, j) {
    const temp = this.heap[i]
    this.heap[i] = this.heap[j]
    this.heap[j] = temp
  }
}

Finding the kth largest element in an array in less than quadratic time is simple with a MaxHeap . Add all values to the heap, then pop until you reach the kth, which should be returned.


// typeof nums === "object" (array of numbers)
// typeof k === "number"

const findKthLargest = (nums, k) => {
    
    const heap = new MaxHeap()
    for(let i=0; i < nums.length; i++){
        heap.insert(nums[i])
    }

    for(let i = 1; i < k; i++){
        heap.extractMax()
    }

    return heap.extractMax()

}

To find the median from a data stream, you can assign a function to a variable and within the function set an array under .this. To add a number, binary search can be conducted to find the insertion point. You can use binary search until the left and right pointers cross, and have a condition of the floored mid value being less than the number to insert leading left being set to mid+1, else setting right to mid-1. Setting “past” the mid from both directions is required when the binary search does not resolve until the pointers cross. Finally for insert, you can insert the number with a splice. To actually find a number, if the length of the array is odd yuu can simply return the floored mid. Else, sum the value at mid and mid-1 and return that value divided by 2.



const MedianFinder = function() {
    this.array = []
}

// typeof num === "number"
MedianFinder.prototype.addNum = function(num) {
   
    let left = 0
    let right = this.array.length-1
    
    while(left <= right){
            const mid = left + Math.floor((right - left)/2)
            
            if(this.array[mid] < num) left = mid + 1
            else right = mid - 1
    }
    
    this.array.splice(left, 0, num)
    
}

MedianFinder.prototype.findMedian = function() {

    const mid = Math.floor(this.array.length/2)

    if(this.array.length%2) return this.array[mid]
    return (this.array[mid] + this.array[mid-1])/ 2

}

To merge a number of ascending-sorted linked lists into a list that is still sorted, all values can be put into a data structure and then removed in a sorted way and put into a new linked list. A priority queue organizing nodes by value or a heap organizing values can do this by making sure the node or value of the greatest amount is always on top, but because neither is a native JavaScript data structure, a sort can be used after pushing all values to an array.


// type of lists === "object" (array of linked lists with next and val properties)

const mergeKLists = (lists) => {

  const sortable = []
  for (let node of lists){
      while(typeof node?.val === "number"){
          sortable.push(node.val)
          node = node.next
      }
  }
  sortable.sort((a,b) => a-b)
  const link = new ListNode()
  let pointer = link
  for (let value of sortable){
      pointer.next = new ListNode(value)
      pointer = pointer.next
  }
  return link.next

}

To find the smallest range of elements covering numbers from a number of arrays that are sorted non-decreasingly, you can use a solution that involves a priority queue implemented as a heap. This priority queue stores multiple values but only sorts one value. The implementation of the heap is similar to that given in MaxHeap , with two modifications: the comparators in siftUp and siftDown are reversed so that minimums end up on top, and only compare the zeroth element at the value instead of the value itself. The main algorithm begins by instantiating a minimum priority queue, and then to the priority queue adds triples of the first number in a given array, its index of 0, and a pointer at the array itself. The maximumSeen number of these firsts is also stored. A rangeStart is instantiated to 0, and a rangeEnd is instantiated to Infinity. While the priority queue is not empty, it can be popped. if rangeEnd less rangeStart is greater than the maximum number seen less the first number in the popped triple, update rangeStart to that number and rangeEnd to the maximum number. If the array length which can be found through the last location of the popped triple is greater than one more than the index stored in the middle of the triple, this means the next index in the array from the triple can be viewed. Insert into the priority queue a triple of the array value at this next index, the next index itself, and the array from the last popped location. Then update the maximum if the value added to the priority queue is the new maximum. The loop ends when this insertion can no longer be done on at least on of the arrays being reviewed. At that point, you can return rangeStart and rangeEnd. This math works because at any given point the priority queue contains one item for each array being reviewed, and will stop when this is no longer the case. It will review every number in every array starting with the minimum number regardless of what array it is from, and since the maximum of the lowest values was already collected in the set-up loop, and is always updated when new items are added to the priority queue, at every step in the loop, a valid minimum and maximum are being considered to see if they have smaller range than the current best pair.


// typeof nums === "object" (array of arrays of numbers)

const smallestRange = (nums) => {
    const minPQ = new minPriorityQueue()

    let maximum = -Infinity
	
    for (let num of nums) {
        minPQ.insert([num[0], 0, num])
        maximum = Math.max(maximum, num[0])
    }

    let rangeStart = 0
    let rangeEnd = Infinity
	
    while (minPQ.length() === nums.length) {
        const [value, index, list] = minPQ.extractMin()

        if (rangeEnd - rangeStart > maximum - value) {
            rangeStart = value
            rangeEnd = maximum
        }

        
        if (list.length > index + 1) {
            minPQ.insert([list[index + 1], index + 1, list])
            maximum = Math.max(maximum, list[index + 1])
        }

    }
    
    return [rangeStart, rangeEnd]
}


class minPriorityQueue {
  constructor() {
    this.heap = []
  }

  insert(val) {
    this.heap.push(val)
    this.siftUp(this.heap.length - 1)
  }

  extractMin() {
    if (this.isEmpty()) return null
    const min = this.heap[0]
    if (this.heap.length > 1){
        this.heap[0] = this.heap.pop()
        this.siftDown(0)
    } else {
        this.heap.pop()
    }

    return min
  }

  peekMin() {
    return this.isEmpty() ? null : this.heap[0]
  }

  isEmpty() {
    return this.heap.length === 0
  }

  length() {
      return this.heap.length
  }

  siftUp(index) {
    while (index > 0) {
      const parentIndex = Math.floor((index - 1) / 2);
      if (this.heap[index][0] < this.heap[parentIndex][0]) {
        this.swap(index, parentIndex)
        index = parentIndex
      } else {
        break
      }
    }
  }

  siftDown(index) {
    while (true) {
      const leftIndex = index * 2 + 1
      const rightIndex = index * 2 + 2
      let minIndex = index
      if (leftIndex < this.heap.length && this.heap[leftIndex][0] < this.heap[minIndex][0]) {
        minIndex = leftIndex
      }
      if (rightIndex < this.heap.length && this.heap[rightIndex][0] < this.heap[minIndex][0]) {
        minIndex = rightIndex
      }
      if (minIndex !== index) {
        this.swap(index, minIndex)
        index = minIndex
      } else {
        break
      }
    }
  }

  swap(i, j) {
    const temp = this.heap[i]
    this.heap[i] = this.heap[j]
    this.heap[j] = temp
  }
}

Tries

A trie represents words with as much overlapping prefixes as possible. To insert a word, start with an object, and for every letter of the word, set the letter’s key in the outer object (if never seen there before) to a new object, and then go into that object. When the word is over, in the object of the final letter, add a .end key. Searching is similar. Descend from the top object without being able to insert anything on misses and return true only if a .end key is found. Checking if a prefix (startsWith) is available is a relaxed search from the top that doesn’t care about .end.


// typeof word === "string"
// typeof prefix === "string"

const Trie = function() {
    this.trie = {}
}

Trie.prototype.insert = function(word) {
    let trie = this.trie
    for (let letter of word) {
        !trie[letter] && ( trie[letter] = {} )
        trie = trie[letter]
    }
    trie.end = true
}

Trie.prototype.search = function(word) {
    let trie = this.trie
    for (let letter of word) {
        if (!trie[letter]) return false
        trie = trie[letter]
    }
    return !!trie.end
}

Trie.prototype.startsWith = function(prefix) {
    let trie = this.trie
    for (let letter of prefix) {
        if (!trie[letter]) return false
        trie = trie[letter]
    }
    return true
}

How to determine if a string is composed of any combination (including repeats) of words in a wordDict? Create a cache map, and then run a helper function go which takes as an argument the string to be tested. If the string is empty, return true, and if the string in go is in the cache, return whatever boolean the cache has. Else, slice at every location in the string argument starting with the 1st index. When the string up to the index is in the wordDict and the remainder of the string returns true from go, set the cache for the string to true and return true. Else set the cache for the string to false and return false.


// typeof s === "string"
// typeof wordDict === "object" (array of strings)

const wordBreak = (s, wordDict) => {

    const cache = new Map()

    const go = (s) => {
        if (cache.has(s)) return cache.get(s)
        if (!s) return true
        for (let i = 1; i <= s.length; i++) {
            if (
                wordDict.includes(s.slice(0,i)) &&
                go(s.slice(i))
            ){
                cache.set(s, true)
                return true
            }
        }
        cache.set(s, false)
        return false
    }

    return go(s)

}

To create a trie that can search for words that include . characters that can match any letter, add words as in Implement Trie (Prefix Tree) but modify the search. At the length of a detected word plus 1, as before return the boolean value of if there is a .end, but wrap this as a depth-first recursive search in a helper function. After the .length check, proceed down a level as normal if the character at that location in the input word is not a ., otherwise loop the characters at the trie location and recursively call go for every character, returning true if any return true, and making sure to gracefully fail if end becomes an argument for go. Return false if the pattern is never found.



const WordDictionary = function() {
    this.trie = {}
}

WordDictionary.prototype.addWord = function(word) {
    let trie = this.trie
    for (let letter of word) {
        !trie[letter] && ( trie[letter] = {} )
        trie = trie[letter]
    }
    trie.end = true
}

WordDictionary.prototype.search = function(word) {
    
    const go = (trie, depth) => {
        if (word.length === depth) return !!trie.end
        if (word[depth] !== '.'){
            return !!(trie[word[depth]] && go(trie[word[depth]], depth+1))
        } else {
            const characterSteps = Object.keys(trie)
            for (let characterStep of characterSteps){
                if (go(trie[characterStep], depth+1)) return true
            }
            return false
        }
    }
    
    return go(this.trie, 0)

}

To design an in-memory file system with ls, mkdir, addContentToFile, and readContentFromFile methods, you can use a FileSystem class and set at its this.root a new instance of a File class. To find ls, you can split an input path by /, and proceed until the second-to-last step, traversing down through .files on the active location. If the goal isFile you can return the folder structure at the current level, sorted, otherwise go down a level and do the same. The mkdir method is simpler and simply traverses downwards from the root, create new File class instances as needed. In addContentToFile, another similar traversal can take place, creating Files as needed, and finally adding some input content to the ending location while adding a isFile flag to help with the ls command. Finally, in readContentFromFile, a traversal takes place downwards and the content is output.



class File {
    constructor () {
      this.isFile = false
      this.files = {}
      this.content = ""
    }
}

class FileSystem {

    constructor () {
      this.root = new File()
    }

    ls(path) {
        let location = this.root
        let files = []
        if (path !== "/") {
            const segments = path.split("/")
            for (let i = 1; i < segments.length; i++) {
                location = location.files[segments[i]]
            }
            if (location.isfile) {
                files.push(segments[segments.length - 1])
                return files.sort()
            }
        }
        files = Object.keys(location.files)
        files.sort()
        return files
    }

    mkdir(path) {
        let location = this.root
        const segments = path.split("/")
        for (let i = 1; i < segments.length; i++) {
            if (!location.files[segments[i]]){
              location.files[segments[i]] = new File()
            }
            location = location.files[segments[i]]
        }
    }

    addContentToFile(filePath, content) {
        let location = this.root
        const segments = filePath.split("/")
        for (let i = 1; i < segments.length - 1; i++) {
            location = location.files[segments[i]]
        }
        if (!location.files[segments[segments.length - 1]]){
          location.files[segments[segments.length - 1]] = new File()
        }
        location = location.files[segments[segments.length - 1]]
        location.isfile = true
        location.content = location.content + content
    }

    readContentFromFile(filePath) {
        let location = this.root
        const segments = filePath.split("/")
        for (let i = 1; i < segments.length - 1; i++) {
            location = location.files[segments[i]]
        }
        return location.files[segments[segments.length - 1]].content
    }
}

Recursion

To find all possible permutations of an array of integers, you can use a helper function go. This takes two arguments: remainingUnusedNumbers,which starts as the input array, and buildingPermutation, which starts as an empty array. If remainingUnusedNumbers is empty, the recursive base case is reached – push buildingPermutation to a results array outside go and return. Else loop remainingUnusedNumbers, at every step generating a new go function slicing out the highlighted number and adding that number to a consistant end of the buildingPermutation array.


// typeof nums === "object" (array of numbers)

const permute = (nums) => {

    const results = []
    
    const go = (remainingUnusedNumbers,buildingPermutation) => {
        if(!remainingUnusedNumbers.length){
            results.push(buildingPermutation)
            return
        }
        for (let i = 0; i < remainingUnusedNumbers.length; i++){
            go(remainingUnusedNumbers.slice(0,i).concat(remainingUnusedNumbers.slice(i+1)),[remainingUnusedNumbers[i],...buildingPermutation])
        }
    }
    
    go(nums,[])
    return results

}

How to find all subsets of an array? Create an array results and fill it with a single empty array. Then loop the array you wish to find all subsets for. For each value, create an empty next array, and loop the contents of results. The results array should have every possible subset up to but not including the currently reviewed element. Therefore, push the concatenation of every array in results with the currently reviewed element into next, and when the inner loop is done, concatenate results and next.


// typeof nums === "object" (array of numbers)

const subsets = (nums) => {

    let results = [[]]

    for(let num of nums){
        const next = []
        for(let array of results){
            next.push(array.concat(num))
        }
        results = results.concat(next)
    }

    return results

}

To find all letter combinations of a phone number, you can return an empty array if there is no number, else seed a new results array with an empty string. Create a letterLookup mapping digits to the letters they might represent, then loop through each digit of the input number. For each digit, create a next array, and loop through each possibleLetterCombination stored in results. Within each possibleLetterCombination, for each newLetter returned by the letterLookup for the digit, push possibleLetterCombination + newLetter to next. When the results array is looped through (one of every valid newLetter has been added to every possibleLetterCombination in results, so next should have about three times more combinations than results), replace results with next. When all digits are exhausted results should contain the answer.


// typeof digits === "string"

const letterCombinations = (digits) => {

    if (!digits) return []
    let results = [""]
    const letterLookup = {
        "2":"abc",
        "3":"def",
        "4":"ghi",
        "5":"jkl",
        "6":"mno",
        "7":"pqrs",
        "8":"tuv",
        "9":"wxyz"
    }
    for (let digit of digits){
        const letters = letterLookup[digit]
        const next = []
        for (let possibleLetterCombination of results){
            for (let newLetter of letters){
                next.push(possibleLetterCombination + newLetter)
            }
        }
        results = next
    }

    return results

}

How to modify an array of integers such that the number increases to its next greatest permutation, or moves to the lowest permutation if it is on the highest? Note that the upperReplacedDigit that needs to move is the first number from left to right that is lower than a number to its right. If there is no such number, the permutation is at a maximum and the next permutation is simply the reverse. Otherwise, start again from the right (the lowest digit) and find the first number that is greater than the upperReplacedDigit but a lower digit. If no such number is found, again, this a sign the array is at a maximum and the next permutation is the reverse. Otherwise, swap the upperReplacedDigit with its update, and then reverse every digit lower than the index of the original upperReplacedDigit. This is because those numbers were in increasing order and we want to move them to their lowest-value permutation.


// typeof nums === "object" (array of numbers)

const nextPermutation = (nums) => {

    let upperReplacedDigit = nums.length - 1
    while(upperReplacedDigit && nums[upperReplacedDigit] <= nums[--upperReplacedDigit]){}
    
    let update = nums.length - 1
    while(nums[update] <= nums[upperReplacedDigit]){
        if(update === upperReplacedDigit) return nums.reverse()
        update--
    }

    [nums[upperReplacedDigit], nums[update]] = [nums[update], nums[upperReplacedDigit]]
    const reverse = nums.splice(upperReplacedDigit+1).reverse()
    nums.splice(upperReplacedDigit+1,0,...reverse)
}

To generate all strings of valid parentheses off of an input number, recursion can be used. One method involves running a helper function that keeps track of openCount and closeCount (both starting at 0), as well as a builder string (starting as empty). At each step in the helper function, immediately return if openCount is ever greater than the input number, and if both the openCount and closeCount are equal to the input number, push builder to results and return. Else run a recursive call of the helper function with one new ( (noting this in the openCount and builder arguments), and if closeCount is less than openCount, run a recursive call of the helper function with one new ) (noted by closeCount and builder).


// typeof n === "number"

const generateParenthesis = (n) => {

    const results = []

    const go = (openCount, closeCount, builder) => {
        if (n < openCount) return
        if (n === openCount && openCount === closeCount) return results.push(builder)
        go(openCount + 1, closeCount, builder + "(")
        if (closeCount < openCount) go(openCount, closeCount + 1, builder + ")")
    }

    go(0, 0, "")

    return results

}

To return all solutions of an input number of chess queen placements on a board of input size such that the queens do not attack each other, a backtracking strategy will help find the correct reults. First, fill a chessboard such that it represents an empty square grid of length and width equal to the input size. Then, run a recursive backtrack function with the input being the staring row, 0. If the row ever equals the input size, the function is attempting to review a row that is out of scope, so join a copy of the chessboard into the expected output shape, push it to results, and return. Else, loop the columns of this row. At every grid position in the row, check the spot isValidQueenPosition. If so, place a marker for the queen on the chessboard being used for the outer solution function, and run backtrack on the next row (since previous rows would have already been reviewed and only one queen per row is possible). When the recursive function series finishes, replace the location with an empty space and continue testing spaces along the row. The only calls that will ever reach past the last row in the chessboard will have valid sets of queens. In terms of what the isValidQueenPosition function looks like, it has three checks that must pass. First, there must not be a queen earlier on the column. Second, there must not be a queen to the diagonal upper-left. Third, there must not be a queen to the diagonal upper-right. The row does not need to be checked because the helper function guarantees there is only ever one queen on a row, and directions down do not matter because they have not been traversed yet in the outer function, and are guaranteed not to have a queen.


// typeof n === "number"

const solveNQueens = (n) => {

    const chessboard = new Array(n)
    for(let i = 0; i < n; i++) {
        chessboard[i] = new Array(n).fill(".")
    }
    
    const results = []
    
    const isValidQueenPosition = (row, col) => {
        for(let i = 0; i < row; i++) {
            if(chessboard[i][col] === "Q") return false
        }
        for(let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if(chessboard[i][j] === "Q") return false
        }
        for(let i = row - 1, j = col + 1; i >= 0 && j <= n - 1; i--, j++) {
            if(chessboard[i][j] === "Q") return false
        }
        return true
    }
    
    const backtrack = (row) => {
        if(row === n) {
            results.push([...chessboard].map(row => row.join('')))
            return
        }
        for(let col = 0; col < n; col++) {
            if(isValidQueenPosition(row, col)) {
                chessboard[row][col] = "Q"
                backtrack(row + 1)
                chessboard[row][col] = "."
            }
        }
    }
    backtrack(0)
    return results

}

Matricies

How to return elements of a matrix in spiral order? (The top row left-to-right, continued by the left side going down, continued by the bottom row right-to-left, continued by the right side going up, continued by the second-to-top row left-to-right, etc.) If the matrix is empty, return an empty matrix. Then, create a result matrix, and start four pointers, left, right, top, and bottom, which indicate the edges of the matrix, inclusive, that still need to be investigated. While the size of the matrix has not been fully explored, step across, down, back, and up the array by using the pointers, pushing to result. Pull each pointer one step closer to the middle of the array after its fourth of the while loop is complete. Be careful not to move pointers in quarters where no elements are left to be pushed.


// typeof matrix === "object" (array of arrays of numbers)

const spiralOrder = (matrix) => {
    if (!matrix.length || !matrix[0].length) return [];
    
    const result = []
    let left = 0
    let right = matrix[0].length - 1
    let top = 0
    let bottom = matrix.length - 1
    let count = matrix.length * matrix[0].length
    
    while (true) {
        for (let i = left; i <= right; i++) {
            result.push(matrix[top][i])
            count--
        }
        top++

        for (let i = top; i <= bottom; i++) {
            result.push(matrix[i][right])
            count--
        }
        if(!count) break
        right--

        for (let i = right; i >= left; i--) {
            result.push(matrix[bottom][i])
            count--
        }
        bottom--

        for (let i = bottom; i >= top; i--) {
            result.push(matrix[i][left])
            count--
        }
        if(!count) break      
        left++
    }
    
    return result

}

To determine is a Sudoku board is valid, you can loop the board, filling a seen set as you go. For every location with a number, check and then add three strings to the set: a row location with the value, a column location with the value, and a 3x3 box location to the value. If any duplicate is found, immmediately return false, else you can return true.


// typeof board === "object" (array of arrays of 1 character strings)

const isValidSudoku = (board) => {

    const seen = new Set()

    for (let i = 0; i < 9; i++) {
      for (let j = 0; j < 9; j++) {
        const value = board[i][j]
        if(value != '.') {
            const row = `row ${i} ${value}`
            const column = `column ${j} ${value}`
            const box = `box ${Math.floor(i/3)} ${Math.floor(j/3)} ${value}`
        
            if(seen.has(row) || seen.has(column) || seen.has(box)) return false

            seen.add(row)
            seen.add(column)
            seen.add(box)

          }
      }
    }
    return true

}

To rotate a 2D square array in-place, 90 degrees to the right, you can notice that points can be moved in sets of four, which means that once a “triangle-like” quarter of the array starts cycles from all its points, the transformation is complete. To find the “triangle-like” array, create and inner an outer loop. The outer loop descends through every row of the input matrix (in a standard way), while the inner loop crosses rows by starting at the column index, and ending at 1 less than the column index, minus an additional value equal to the row. To understand why the inner loop starts and ends where it does, consider that the inner loop must end at least one step before the end of row because otherwise there would not be room for the start of the next clockwise side to start at a corner. The -1 to both sides for each step into the matrix then makes sense because each step into the matrix removes the leftmost and the rightmost columns. Finally, for the rotation itself, save the value you find at the given location. Move into this position the value found at the [sideLength-1-column][row] of itself, and continue this pattern all four-step cycles are completed starting from every place the “triangle” has. Used the saved value to fill the last of the four positions.


// typeof matrix === "object" (array of arrays of numbers)

const rotate = (matrix) => {

    const sideLength = matrix.length
    for(let row = 0; row < sideLength; row++){ 
        for(let column = row; column < sideLength - 1 - row; column++){

            const startValue = matrix[row][column]

            matrix[row][column] = matrix[sideLength-1-column][row]
            matrix[sideLength-1-column][row] = matrix[sideLength-1-row][sideLength-1-column]
            matrix[sideLength-1-row][sideLength-1-column] = matrix[column][sideLength-1-row]
            matrix[column][sideLength-1-row] = startValue

        }  
    }
    
    return matrix

}

To set 0 to all rows and columns in a matrix that contain at least one 0, in-place, parse through every row or column and if a 0 is noted, mark every nonzero element in the row or column with an intermediate value like a “c”. Then parse though whichever you did not first check, row or column, and if a 0 is noted, mark every nonzero element in the row or column with a 0. Then go through the matrix one final time and update every intermediate value to a 0 (it can no longer confuse the second scan).


// typeof matrix === "object" (array of arrays of numbers)

const setZeroes = (matrix) => {
    for (let i = 0; i < matrix.length; i++){
        let clear = false
        for(let j = 0; j < matrix[0].length; j++){
            if (!matrix[i][j]){
                clear = true
                break
            }
        }
        if (!clear) continue
        for(let j = 0; j < matrix[0].length; j++){
           if(typeof matrix[i][j] === "number" && matrix[i][j]) matrix[i][j] = "c" 
        }
    }
    
    for (let j = 0; j < matrix[0].length; j++){
        let clear = false
        for(let i = 0; i < matrix.length; i++){
            if (!matrix[i][j]){
                clear = true
                break
            }        
        }
        if (!clear) continue
        for(let i = 0; i < matrix.length; i++){
           matrix[i][j] = 0
        }
    }
    
    
    for (let i = 0; i < matrix.length; i++){
        for(let j = 0; j < matrix[0].length; j++){
            if(matrix[i][j] === "c") matrix[i][j] = 0
        }
    }
    
    return matrix

}

To solve a Sudoku board with some numbers already filled out, in-place, loop the input board. At each location, compute a unique indentifier for which 3x3 square the location is in. Sudoku boards are 9x9, and every row, column, and 3x3 square (of which there are 9) must contain the numbers 1 through 9. At each location, using a storage for rows, columns, and squares outside the loop, add a set if it does not already exist to each of the three with a key corresponding to the row, column, and square being reviewed. Within each set, add the value at the location if is not empty (empty is indicated by “.” in LeetCode). If the space is empty, add an array with the row and column to a visit array outside the loop. After finishing the original traversal, you can run a depth-first search using a helper function. In this depth-first search helper, you can immediately return true if the visit array is empty (meaning there are no empty spaces where values need to be planted). Else note the row and column of the 0 index item in visit and compute the associated square. Loop the numbers 1 through 9, and if any can be planted at the given row and column (check rows, columns, and squares to see what is available), add the value at the location on the board and add the value also to rows, columns, and squares in the appropriate set as a note. Shift off the item from visit that was just processed, and run the depth-first search recurisvely. If this returns true you can return true, else restore the location to being empty and remove the note of the tested value from rows, columns, and squares, then unshift an array with row and column back to the front of the visit array. At the end of this process the board should be filled correctly.


// typeof board === "object" (array of arrays of 1 character strings)

const solveSudoku = (board) => {

    const rows = {}
    const cols = {}
    const squares = {}
    const visit = []
    for (let row = 0; row < 9; row++) {
        for (let column = 0; column < 9; column++) {
            const square = Math.floor(row / 3) + "+" + Math.floor(column / 3)
            rows[row] = rows[row] || new Set()
            cols[column] = cols[column] || new Set()
            squares[square] = squares[square] || new Set()
            if (board[row][column] != ".") {
                rows[row].add(board[row][column])
                cols[column].add(board[row][column])
                squares[square].add(board[row][column])
            } else{
                visit.push([row, column])
            }
        }
    }
    const dfs = () => {
       if (!visit[0]) {
           return true
       }
        const [row, column] = visit[0]
        const square = Math.floor(row / 3) + "+" + Math.floor(column / 3)
        for (let dig = 1; dig < 10; dig++) {
            dig = String(dig)
            if (!(rows[row].has(dig)) && !(cols[column].has(dig)) && !(squares[square].has(dig))) {
                board[row][column] = dig
                rows[row].add(dig)
                cols[column].add(dig)
                squares[square].add(dig)
                visit.shift()
                if (dfs()) {
                    return true
                } else {
                    board[row][column] = "."
                    rows[row].delete(dig)
                    cols[column].delete(dig)
                    squares[square].delete(dig)
                    visit.unshift([row, column])
                }
            }
        }
        return false
    }
    dfs()

}

Queues

To design a hit counter that counts hits in the last 300 seconds, you can assign a function to a variable that contains a map under this. To add a hit, add a count to the second provided in the map, creating a new entry if that time has not before been seen. To look up hits, check 300 values starting at the second being checked and working backwards.



const HitCounter = function () {
  this.map = new Map()
}

HitCounter.prototype.hit = function (timestamp) {
  if (!this.map.has(timestamp)){
    this.map.set(timestamp,1)
  } else {
    this.map.set(timestamp, this.map.get(timestamp)+1)
  }
}

HitCounter.prototype.getHits = function (timestamp) {
  let result = 0
  for (let i = 0; i < 300; i++) {
    if (this.map.has(timestamp-i)){
      result += this.map.get(timestamp-i)
    }
  }
  return result

}