Problem Solving Patterns

Source: JavaScript Algorithms and Data Structures Masterclass

Frequency Counter

When counting frequency of iterable objects, avoid nested looping that results in O(n^2), by using objects or sets to collect values.

Example

Create a function named “same” that compares 2 arrays, and check if one array contains the squared values of the other, with matching frequencies

The Naive Solution

Use nested loop to check if squared value of current looping element exists.

Result: O(n^2)

Refactoring using Frequency Counter

Use multiple loops instead of nested loops, create objects of each array, with key of elements of array, and value of frequency.

Result: O(3n) === O(n)

Anagram

Create a function that validates if a string is an anagram of the other.

// My initial solution
// Use same pattern as above
const validAnagram = (str1, str2) => {
    if (str1.length !== str2.length) {
        return false;
    };

    str1Obj = {};
    str2Obj = {};

    for (s of str1) {
        str1Obj[s] = (str1Obj[s] || 0) + 1;
    };
    for (s of str2) {
        str2Obj[s] = (str2Obj[s] || 0) + 1;
    };

    for (s in str1Obj) {
        if(!str2Obj[s] || str1Obj[s] !== str2Obj[s]) {
            return false
        };
    }
    return true;
}

// Model Solution
// Create 1 object => loop over 2nd string and subtract from obj1, checking if 0

const validAnagram = (str1, str2) => {
    if(str1.length !== str2.length) {
        return false;
    }

    str1Obj = {}

    for (s of str1) {
        str1Obj[s] = (str1Obj[s] || 0) + 1;
    }

    for (c of str2) {
        if(!str1Obj[c] || str1Obj[c] === 0) {
            return false;
        } else {
            strObj[c]--;
        }
    }
    return true;
}

Multiple Pointers

When searching for a pair that meets a certain condition, use two references(pointers) and work towards the middle

Example

From a sorted Array, find the first pair of which the sum of the pair equals 0.

Naive Solution

Create a nested loop for each element of the loop, check each sum

Result: O(n^2)

Refactored with Multiple Pointer

Start with two indicators that is located on each end of the array. While the left pointer is smaller than the right pointer, if the sum of the two is positive move right pointer -1, and if negative move left pointer + 1. If this is 0, that is the answer.

const countUniqueValues = (arr) => {
    let i = 0;
    for (let j = 1; j <= arr.length; j++) {  // although j is supposed to end at length - 1, this results in one final increment of i without actually having to do so
        if (arr[i] !== arr[j]) {
            arr[++i] = arr[j]
        }
    }
    return i
}

Sliding Window

Used when dealing with a subset of data that is continuous.

Example

Create a function that accepts an array and a number. Find the largest sum of the subset with the length of the given number in the array.

Naive Solution

Using nested loop, for each element of the array, get sum of each subset, compare it to the current max value. Result: O(n^2)

Refactoring with Sliding Window

Instead of the nested loop, move the subset as a whole by subtracting the very first element and adding the upcoming element.

function maxSubarraySum(arr, num) {
	let maxSum = 0;
	let tempSum = 0;
	if (arr.length < num) return null;

    // Get sum of first subset
	for (let i = 0; i < num; i++) {
		maxSum += arr[i];
	}
	tempSum = maxSum;

    // Loop array, subtract the current first number, add next number
	for (let i = num; i < arr.length; i++) {
		tempSum = tempSum - arr[i-num] + arr[i];
		maxSum = Math.max(maxSum, tempSum);
	}
	return maxSum;
}