├── README.md
├── day-one
├── README.md
├── images
│ ├── invalid-mbh-1.png
│ └── invalid-mbh-2.png
└── sorting-and-disorder.js
└── day-two
├── README.md
└── dynamic-programming.js
/README.md:
--------------------------------------------------------------------------------
1 | # Algorithms Elective
2 |
3 | Refer to the READMEs in `day-one` and `day-two` for instructions.
4 |
--------------------------------------------------------------------------------
/day-one/README.md:
--------------------------------------------------------------------------------
1 | # Advanced Sorting and Disorder
2 |
3 | On Day one of our algorithms elective, we'll focus on advanced topics in arrays.
4 |
5 | # Sorting Algorithms
6 |
7 | ## Quick Sort
8 |
9 | The idea of quick sort is simple:
10 | 1. Choose a number in the array. We will call this the pivot.
11 | 1. Reorder the array so that numbers less than or equal to the pivot are to the left of the pivot, and numbers greater than the pivot are on the right of the pivot.
12 | 1. Recursively repeat this process on the left side of the array (not including the pivot) and the right side of the array (also not including the pivot). We reach the base case when we try to sort an array with length less than or equal to one.
13 |
14 | There are many variations of quick sort - they all share the approach outlined above, but differ in how they choose a pivot and reorder the array around the pivot.
15 |
16 | Quick sort *typically* completes in O(n log(n)) time, and in most cases completes faster than other O(n log(n)) sorting algorithms. However, it performs very poorly in some cases, having a worst case time complexity of O(n2).
17 |
18 | Quick Sorts are commonly divided into two parts. The main `quicksort` function implements the logic outlined above. A second helper function, called a partition function, chooses a pivot and reorders the array around the pivot. See below for details.
19 |
20 | ### partition functions
21 | A partition function accepts three parameters:
22 |
23 | 1. `arr`: an array to reorganize.
24 | 1. `lo`: an integer representing an index in the array.
25 | 1. `hi`: also an integer representing an index in the array. The `hi` and `lo` parameters define the part of the array we want to reorganize.
26 |
27 | A partition function will pick a pivot between indexes `lo` and `hi` (this is an *inclusive* range). Working in place, the partition function will reorder the array between (and including) indexes `lo` and `hi`, so that everything elements less than/equal to the pivot are on the left side of the pivot and elements larger than the pivot appear to the right of the pivot. The partition function should return the index of the pivot after reordering.
28 |
29 | Partition functions should have time complexity O(hi - lo).
30 |
31 | ## Heap Sort
32 |
33 | Heap sorting algorithms are closely connected to a data structure called a Max Binary Heap (Note that this has no relation to the "heap" we talk about in the context of memory management). Let's talk about this data structure.
34 |
35 | ### Max Binary Heap (MBH)
36 |
37 | A max binary heap is a type of binary tree. Each node in the tree stores a value, as well as a left sub-tree and a right subtree. The left and right subtrees may be null. are familiar with, we guarantee that all values in the left subtree are smaller than or equal to the value of the root node, and we guarantee that all values in the right subtree are greater than the value of the root node.
38 |
39 | In contrast, a max binary heap guarantees that all values in both the left and right subtree are less than or equal to the value of the root node. Therefore, the value of the root node in a max binary heap *must be the largest value in the heap*. Consider the heap below:
40 |
41 | 
42 |
43 | We can store it in an array like so: `[100, 19, 36, 17, 3, 25, 1, 2, 7]`. We store the root value in the 0th index of the array. Then we store the next level, `19` and `36` from left to right. Then we proceed to the next level, and so on.
44 |
45 | ### Building an MBH
46 |
47 | Heap sort starts from the premise that we can interpret any array as if it were a heap. However, most arrays wont correspond to valid heaps. For instance, we would interpret the array `[1,2,4,3,7,6,5]` as the *invalid* heap:
48 |
49 | .
50 |
51 | To begin a heap sort, we first want to reorder an array so that it is a valid max binary heap. How do we do this? Let's start with a simpler question.
52 |
53 | Suppose we have a valid heap, like our example earlier. `[100, 19, 36, 17, 3, 25, 1, 2, 7]`. If we add the number `20` to the end of this array, we will no longer have a valid heap. *Why is this no longer a valid heap?* Our new array, `[100, 19, 36, 17, 3, 25, 1, 2, 7, 20]`, corresponds to the heap:
54 |
55 | 
56 |
57 | Since `20 > 3`, we no longer have a valid heap. To fix this, we must "sift up" the value `20` until we have a valid heap again. We swap `20` with `3`:
58 |
59 | 
60 |
61 | Since `20 > 19` this is still an invalid heap. We have to sift `20` one level higher to obtain a valid heap.
62 |
63 | Now that we know how to "sift up", we can turn any array into a valid MBH with these steps:
64 |
65 | 1. Consider the first element alone. It forms a valid MBH.
66 | 1. Consider the first two elements as an MBH, and sift up the last element. Now the first two elements for a valid MBH.
67 | 1. Consider the first three elements as an MBH, and sift up the last element. Now the first three elements form a valid MBH.
68 | 1. Continue iterating through the array, each time sifting up the current element.
69 |
70 | ### Sifting Down
71 | Suppose, on the other hand, that we have a completely valid MBH except for the root node. We can turn it into a valid MBH by "sifting the root node down" to where it belongs, according to the following steps:
72 |
73 | 1. Find the larger of the two child nodes. If the larger of the two child nodes is larger than the root node, swap them.
74 | 1. Continue this until you hit the end of the heap or the value we are sifting down is larger than both its children.
75 |
76 |
77 | ### Overview of Heap Sort
78 |
79 | 1. Reorder the array so that it forms a valid MBH (buildMaxHeap)
80 | 1. Since we have a valid MBH, the first element must be the largest element in the array. Swap the first element with the last element.
81 | 1. We no longer consider the last element as part of the heap. The remaining heap may not be a valid MBH since the root was swapped with a different number. Sift down to obtain a valid MBH.
82 | 1. Now the first element in the array is the largest remaining element. Swap it with the last item in the heap.
83 | 1. No longer consider the last two elements in the array as part of the heap. We may not have a valid heap any longer, so we sift down.
84 | 1. Continue this process iteratively till we have a sorted array.
85 |
86 | # Disorder
87 |
88 | After launching Codesmith's CSX platform, we want to understand how people are using the site. There are a large number of coding challenges for users to work on, and the site recommends working on the coding challenges in a particular order. One question we have is - are our users actually completing the challenges in order? Are they starting with the hardest challenges and working backwards? Or are they skipping around randomly?
89 |
90 | Luckily we're able to collect data on each user to see which order they are working on the challenges. For instance, our user Alice began working on challenge 2, then updated her code for challenge 2 again. Afterwards, she switched to working on challenge 4. Finding it difficult, she went backwards to begin challenge 3, and updated her code four times. We represent Alice's history of coding work with the array `[2,2,4,3,3,3,3]`.
91 |
92 | How "in order" did Alice work on code? We are essentially looking for ways to measure the "sortedness", or *disorder* of an array. In the following challenges we will devise algorithms that compute measures of disorder.
93 |
94 | ## Inversions
95 |
96 | Suppose we have an array of integers named `arr`. Then an inversion is a pair of indexes `(i,j)` such that `i < j` but `arr[i] > arr[j]`.
97 |
98 | Create a function that counts the number of inversions in an array. (You should try to achieve this in O(n log(n)) time. Consider modifying a sorting algorithm (maybe another one that we haven't worked on today??) to count inversions as it sorts).
99 |
100 | For example, `inversions([1,7,6,10,4,3])` should be `7` since `[1,7,6,10,4,3]` has `7` inversions (The pairs of indexes `(1,2), (1,4), (1,5), (2,4), (2,5), (3,4), (3,5)`).
101 |
102 | ## Longest Increasing Subsequence (LIS)
103 |
104 | A subsequence of an array is a subarray where elements have the same order relative to the original array. (E.g. `[4,-1,10]` is a subsequence of `[1,4,-1,28,5,10]`, but `[-1,4,10]` is not).
105 |
106 | An increasing subsequence (more accurately, a non-decreasing subsequence), is a subsequence of an array which is itself a sorted array. (E.g. `[1,4,28]` is an increasing subsequence of `[1,4,-1,28,5,10]` but `[4,-1,28]` isn't).
107 |
108 | Write a function that returns the length of the longest possible increasing subsequence of an array. (Note that the longest increasing subsequence of an array may not be unique).
109 |
110 | # Extensions
111 |
112 | After completing the challenges above, work on these extensions to improve your code!
113 |
114 | ## Quick Sort Extensions
115 |
116 | Look into different approaches to writing partition functions. In particular, write a Lomuto partition and a Hoare partition. Come up with examples of arrays that are sorted more efficiently using a Hoare partition. As a way of measuring the relative efficiency of the Lomuto and Hoare partition methods, add functionality into the `createSort` function to count the number of swaps made during sorting.
117 |
118 | ## Heap Sort Extension
119 |
120 | The way we are building our max heap right now has time complexity O(n log(n)). Refactor your buildMaxHeap function so that it has time complexity O(n). See [this video](https://www.youtube.com/watch?v=MiyLo8adrWw) for guidance.
121 |
122 | ## Disorder Extensions
123 |
124 | Extend your LIS function so that it also returns one of the (not necessarily unique) longest increasing subsequences.
125 |
126 | Create your own measure of disorder that accounts for "skipping" numbers even if they are in order. For instance, we want the disorder of [1,4,7] to be higher than the disorder of [2,3,4] since 2, 3, and 4 are consecutive numbers.
127 |
--------------------------------------------------------------------------------
/day-one/images/invalid-mbh-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodesmithLLC/algorithms-elective/19cbf5de7840a8dc25922aaa1ffeaaece8d142f2/day-one/images/invalid-mbh-1.png
--------------------------------------------------------------------------------
/day-one/images/invalid-mbh-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodesmithLLC/algorithms-elective/19cbf5de7840a8dc25922aaa1ffeaaece8d142f2/day-one/images/invalid-mbh-2.png
--------------------------------------------------------------------------------
/day-one/sorting-and-disorder.js:
--------------------------------------------------------------------------------
1 | // Quick Sort
2 |
3 | function quicksort(arr, lo, hi) {
4 |
5 | }
6 |
7 | function partition(arr, lo, hi) {
8 |
9 | }
10 |
11 | // Heap Sort
12 | // Some potentially helpful functions are provided below
13 | const getLeftChildInd = index => (2 * index) + 1
14 | const getRightChildInd = index => (2 * index) + 2
15 | const getParentInd = index => Math.floor((index - 1) / 2)
16 | function swap(arr, i, j) {
17 | const temp = arr[i];
18 | arr[i] = arr[j];
19 | arr[j] = temp;
20 | }
21 |
22 | function siftUp(arr, endOfHeap){
23 |
24 | }
25 |
26 | function buildMaxHeap(arr) {
27 |
28 | }
29 |
30 | function siftDown(arr, endOfHeap){
31 |
32 | }
33 |
34 | function heapsort(arr) {
35 |
36 | }
37 |
38 | // Inversions
39 |
40 | function inversions(arr) {
41 |
42 | }
43 |
44 | // Longest Increasing Subsequence
45 |
46 | function lis(seq) {
47 |
48 | }
49 |
50 | // Quick Sort Extensions
51 | // (please complete heap sort and disorder first)
52 |
53 | function createSort(partition) {
54 | return function quicksort(arr, lo, hi) {
55 |
56 | }
57 | }
58 |
59 | function lomutoPartion() {
60 |
61 | }
62 |
63 | function hoarePartition() {
64 |
65 | }
66 |
67 | const quicksortLomuto = createSort(lomutoPartion);
68 | const quicksortHoare= createSort(hoarePartition);
69 |
--------------------------------------------------------------------------------
/day-two/README.md:
--------------------------------------------------------------------------------
1 | # Problems in Dynamic Programming
2 | *Also known as problems with coins!*
3 |
4 | ## Maximum Value Path
5 |
6 | You are given a square 2-dimensional grid of coin values (i.e. an array of arrays). Starting from the upper left corner, you must make your way down to the bottom right corner. On each move, you can only move down or to the right (you may never move up or left). You get to collect the coin at each square in the grid you step on. Your function should return the maximum amount of money you can collect by travelling from the upper left corner to the bottom right corner.
7 |
8 | ### Extension
9 |
10 | Additionally, your function should return a string representing one of the paths through the coinGrid that earns you the most money (e.g. the string 'DRRRDD' would mean, starting from the upper left corner, move Down, then Right three times, then Down twice).
11 |
12 | ## Longest Common Subsequence (LCS)
13 |
14 | Compute the length of the Longest Common Subsequence of two arrays of integers. Refer to lecture slides for guidance.
15 |
16 | ### Extension
17 |
18 | Additionally, return one of the subsequences that is an LCS of `x` and `y`.
19 |
20 | ## Golomb Suequence
21 |
22 | The [Golomb Sequence](https://en.wikipedia.org/wiki/Golomb_sequence) is a special sequence of integers. It is defined as such:
23 |
24 | 1. The Golomb Sequence is non-decreasing
25 | 1. The first number in the sequence is 1.
26 | 1. The second number in the sequence is 2.
27 | 1. The nth number in the sequence describes how many times the number n appears in the sequence.
28 |
29 | The Golomb sequence begins: `1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, ...`.
30 |
31 | ## Fair Split
32 |
33 | You are given an array of coin values. You must divide the coins amongst your two children. They will throw a severe tantrum unless they receive the fairest split possible (i.e the sum of child 1's coins is as close as possible to the sum of child 2's coins).
34 |
35 | Your function should return an array containing two arrays. The first array is child 1's coins and the second array is child 2's coins.
36 |
37 | ### Extension
38 |
39 | Refactor your function so that it receives an array of coins and the number of children you have. Divide the coins in the fairest possible manner amongst all your children.
40 |
41 | ## Best Strategy
42 |
43 | Your are given an array of (*guess what?*) coins! You are playing a game against a single opponent. You and your opponent alternate turns. On each turn a player may remove one coin from either the beginning of the array or the end of the array and keep the coin. You move first. What is the minimum value of coins you will collect if you play optimally?
44 |
45 | ## Extension Problem: Directed Graphs and Shortest Paths
46 |
47 | First, design a [Directed Graph data structure](https://en.wikipedia.org/wiki/Directed_graph). You may implement this however you desire.
48 |
49 | Then, design a function to determine the shortest point between two nodes in a directed graph. To begin with, you may assume that your graphs are [acyclic](https://en.wikipedia.org/wiki/Directed_acyclic_graph). Then extend your function so that it handles directed graphs that may contain cycles.
50 |
--------------------------------------------------------------------------------
/day-two/dynamic-programming.js:
--------------------------------------------------------------------------------
1 | // Maximum Value Path
2 |
3 | function maxValuePath(coinGrid) {
4 |
5 | }
6 |
7 | // Longest Common Subsequence
8 |
9 | function LCS(x, y) {
10 |
11 | }
12 |
13 | // Golomb Sequence
14 |
15 | function golomb(n) {
16 |
17 | }
18 |
19 |
20 | // Fair Split
21 |
22 | function fairSplit(x) {
23 |
24 | }
25 |
26 | // Best Strategy
27 |
28 | function bestStrategy(coins) {
29 |
30 | }
31 |
32 | // Extension: Shortest Path in DAG
33 | // (first create a Directed Graph data structure)
34 |
35 | function shortestPath(s, v) {
36 |
37 | }
38 |
--------------------------------------------------------------------------------