Arora A. - 101 Algorithms Questions You Must Know - 2018
Arora A. - 101 Algorithms Questions You Must Know - 2018
Arora A. - 101 Algorithms Questions You Must Know - 2018
by
Amrinder Arora
Department of Computer Science,
The George Washington University
101 Algorithms Questions You Must Know
Copyright© 2018, Amrinder Arora
All rights reserved. No part of this book may be reproduced or transmitted in any
form or by any means, electronic or mechanical, including photocopying, recording,
or by an information storage and retrieval system - except by a reviewer who may
quote brief passages in a review to be printed in a magazine or newspaper - without
permission in writing from the publisher.
Table of Contents
Acknowledgments
Section 1: Warm Up Questions
Section 2: Asymptotic Analysis
Section 3: Data Structures, Sorting & Searching
Section 4: Divide and Conquer
Section 5: Greedy Algorithms
Section 6: Dynamic Programming
Section 7: Graph Traversal and Backtracking
Section 8: Branch and Bound
Section 9: NP-Completeness
Section 10: Theory of Lower Bounds
Section 11: Graph Theory
Bibliography
About the Autho r
Solution
We recollect that log (xy) = log x + log y .
Therefore, using log 10 2 = 0.3010 and log 10 3 = 0.4771 , we can
calculate log 10 6 = 0.3010 + 0.4771 = 0.7781
Solution
Suppose S = ∑ i=1 to n i 2 i
–S = 2 (2 n – 1) – n 2 n+1
Solution
While we can always use principle of mathematical induction (PMI)
to solve these kinds of problems, that requires us to know or guess
the solution. If we do not have a good guess, we may need to solve
it directly. The good news is that although it is a bit more
complicated, like the previous question, this question can also be
solved using term sliding.
Subtracting the second term from the first one, we obtain that:
– S = 1 2 2 + (2 2 – 1 2 ) 2 2 + (3 2 – 2 2 ) 2 3 + … + (n 2 – (n-
1) 2 ) 2 n – n 2 2 n+ 1
Since i 2 – (i-1) 2 can be written as 2i-1 , we can now write the
previous equation as:
–S = ∑ i=1 to n (2i-1) 2 i – n 2 2 n+1
Solutio n
Both of these terms can be independently solved and compared.
We can observe that: ∑ 1 to n i 2 = n (n+1) (2n+1)/6 , while ∑ 1 to n*n
= n 2 (n 2 +1)/2 .
Thus, the second term is significantly larger for larger values of n.
Solution
We observe that the outer loop (on counter j ) just increments one by
one. So, that loop runs in O(n) time.
The inner loop runs on counter k , and the value of k gets squared
every time, starting with 2 .
Therefore, the value of k jumps from 2 , to 4 , to 16 to 256 . We
j=1 observe that after m iterations of the loop, the value of
while (j < n) {
k=2 k becomes 2 2^m . The loop terminates when the value
while (k < n) { of k becomes larger or equal to n , that is, 2 2^m ≥ n ,
Sum +=
a[j]*b[k] that is, m ≥ log log (n) . Therefore the inner loop runs
k=k*k in O(log log n) time .
}
j++ Since the two loops are nested, the entire program runs
}
in O(n log log n) time.
Since the loops are nested, the entire program runs in constant, that
is, O(log n log log n) time.
Solution
Since f 1 (n) = O(g 1 (n)) , therefore there exist constants c 1 and n 1 ,
such that f 1 (n) ≤ c 1 g 1 (n)) for all values of n ≥ n 1 .
Similarly, since f 2 (n) = O(g 2 (n)) , therefore there exist constants c
2 and n 2 , such that f 2 (n) ≤ c 2 g 2 (n)) for all values of n ≥ n 2 .
From the two choices of n 1 and n 2, we select the larger value, let us
call it n 3 . Similarly, we select the larger value from c 1 and c 2 , let
us call it c 3 . Therefore, we have:
Solution
Since f 1 (n) = O(g 1 (n)) , therefore there exist constants c 1 and n 1 ,
such that f 1 (n) ≤ c 1 g 1 (n)) for all values of n ≥ n 1 .
Similarly, since f 2 (n) = O(g 2 (n)) , therefore there exist constants c
2 and n 2 , such that f 2 (n) ≤ c 2 g 2 (n)) for all values of n ≥ n 2 .
From the two choices of n 1 and n 2, we select the larger value, let us
call it n 3 . We define a new constant c 3 = c 1 c 2 . Therefore, we
have:
f 1 (n) ≤ c 1 g 1 (n)) for all values of n ≥ n 3
Solution
We would like to evaluate lim n->infinity f(n)/g(n) , and we again use
the helpful L’Hopital’s rule, which states that assuming certain
conditions apply, lim n->infinity f(n)/g(n) = lim n->infinity f’(n)/g’(n) ,
where f’(n) and g’(n) represent the first derivatives of functions f(n)
and g(n) respectively.
Applying this to our case, we obtain that:
f’(n) = 2n and g’(n) = 80 (log n) 79 / n
Therefore, we have that:
lim n->infinity f(n)/g(n) = lim n->infinity 2n 2 /80(log n) 79
We can repeat this process a few more times (79 to be precise!), and
at that point, we have that:
lim n->infinity f(n)/g(n) = 2 80 n 2
/80! which obviously approaches
infinity as n tends to infinity .
(sqrt(3)) n = o(2 n )
2 n = o(2.1 n )
2 n = o(4 n )
2 n = o(2 2n ) // This is because 2 2n = 4 n
=0
Therefore, we conclude that 10 n = o(2 n^2 ) .
We can see that after a finite number of steps, this limit can be
evaluated to be 0 .
And of course, we know that n 1.1 = o(n 1.1 log log log n).
Therefore, by using transitivity, we reach the conclusion that:
n log n = o(n 1.1 )
an d
n 1.1 = o(n 1.1 log log log n).
That is,
= O((log n) 4 )
= o(n )
Therefore, n 6 = o(n!)
This can be solved to be T(n) = 9n, and clearly, this is also linear
time.
Solution
We observe that the right most point of the set P must be on the
staircase. By the same logic, the top most point (that is, the point
with the largest y -value) must also be on the staircase. Using this
observation, we can compute the staircase in O(n) time if we are
given the points in P in sorted order. We simply iterate the set of
points from right to left, and include every point that has a y -value
higher than the previously known maximum y -value.
The following pseudocode describes the process:
ymax = negative_infinity
for point p in P in sorted order from right to left { Using this observation we
if p.y > ymax { can come up with a simple
include p in staircase algorithm to compute the
ymax = p.y
} staircase in O(n log n) time.
} Firstly, we simply sort the
points, and then follow the
procedure outlined above.
Solutio n
Suppose the list of lectures are given by their start and end times as:
[[s 1 , e 1 ], [s 2 , e 2 ], …. [s n , e n ]]
Solution
This is the Huffman encoding problem. The intuition behind the
algorithm is that more frequent symbols should have shorter codes,
and less frequent symbols should have longer codes. This can be
implemented using an approach where we take the two least frequent
symbols and combine them into a virtual symbol that appears with a
frequency that is equal to the sum of the individual frequencies of the
two symbols.
This can be implemented using the following greedy algorithm that
utilizes a minimum heap.
1. Initialize an empty encoding tree.
2. Create a min-heap of all the symbols in the
alphabet based on the frequency.
3. While there are more than two symbols in the
alphabet:
a. Extract the two symbols with least
frequencies by invoking the extract
minimum operation two times.
b. Create a node with the sum of two
frequencies and insert it back into
the heap. Also insert this node in
the encoding tree with the two
symbols as its left and right child
nodes.
4. When only two symbols are left, create a root node
for the encoding tree and the two symbols become
the child nodes of the root node.
Algorithm
This leads to a straightforward dynamic programming formulation,
which has mn entries in the dynamic programming table and each
entry can be computed in at most n time, Therefore, the algorithm
runs in O(n 2 m) time.
Improved Algorithm
The time complexity can be improved to O(n m) by observing that
optimal first attempt floor for (n,m) ≥ optimal first attempt floor for
(n-1,m) . So, when executing the loop on n , we don’t need to start
the j counter with an initial value of 1. Therefore, we can compute
all f(n,m) values for one value of m and all values of n in O(n) time .
Question 64. Teleportation
You have a teleporter that can take you from galaxy i to galaxy j .
Cost to teleport is given by c(i,j) > 0 , which can be arbitrary. Some
galaxies are “astro-haunted” - this is specified by a matrix A , where
A[i] can be 0 or 1 (1 means that that galaxy is “astro-haunted”).
Give a polynomial time algorithm that minimizes the cost of going
from galaxy 1 to galaxy n , such that you pass through at most m
astro-haunted galaxies. (You can assume that galaxies 1 and n are
not astro-haunted.)
Solution
We observe that the problem is similar to all pairs shortest path
problem, but in addition to that, we have the constraint that we can
go through at most m astro-haunted galaxies. We model this
constraint also in our notation.
Notation
Let D(i,j,k) denote the cost of the shortest path from i to j using at
most k astro-haunted galaxies.
Recursive Formulation
Recursive formulation on D(i,j,k) can be written on the variable k
and by deciding on the last astro-haunted galaxy on the path from i to
j.
D(i,j,k) = min {
D(i,j,k-1),
min (1≤z≤n | A[z]=1 ) {D(i,z,k-1) + D(z,j,0)}
}
We observe that A[z] = 1 constraint specifies that the galaxy z is astro
haunted.
Further, by taking the minimum with D(i,j,k-1) we satisfy the
constraint of at most k astro-haunted galaxies, while still avoiding
pitfall of leaving this value undefined in case there is no astro
haunted galaxy.
Base Case
Base case D(i,j,0) can be solved simply by using All Pairs Shortest
Path and by eliminating all astro-haunted galaxies.
Algorithm
The algorithm can easily be written in terms of for loops for each of
the indices in the notation of D(i,j,k) . As is often observed, the
index of the main recursive formulation usually forms the outermost
loop.
Time Complexity
Time Complexity of base case: O(n 3 ) from the all pairs shortest
path problem. Each calculation of D(i,j,k) takes O(n) time. Further,
there are kn 2 entries in the dynamic programming table. Therefore,
the total time Complexity of recursive portion: O(k n 3 )
Therefore, the total time complexity is O(k n 3 ).
Question 65. Longest Common Subsequence
Given two strings (sequences of characters), the longest common
subsequence (LCS) problem is to find the longest subsequence (not
necessarily contiguous) that exists in both of the input strings. For
example, given strings “mangoes” and “mementos”, the subsequence
“mnos” is common in both and is in fact the longest common
subsequence. Given two strings of sizes n 1 and n 2 respectively,
find a dynamic programming algorithm to find the longest common
subsequence in O(n 1 n 2 ) time.
Solution
Before we formalize a dynamic programming solution for this
problem, we can explore the recursive nature of this problem.
Given two strings s 1 and s 2 , if the first character of both the strings
is the same, then we can consider that common character to be the
start of the common subsequence, and recursively call the function
on the substrings of s 1 and s 2 after removing the first character. If,
on the other hand, the first character is not the same, then we can
discard the first character from one of the strings and evaluate the
longest common subsequence with the other string. This underlying
recursive idea can now be used to formulate a dynamic programming
solution.
Notation
Let LCS[i,j] represent the length of the longest common subsequence
for string s 1 starting from it’s i- th character and string s 2 starting
from it’s j -th character. We assume the strings to be 0- indexed,
that is, the first character is at 0- th index .
We are interested in LCS[0,0] as our final answer.
Recursive Formulation
The recursive formulation depends upon whether or not s 1 [i] = s 2
[j] .
If s 1 [i] = s 2 [j] :
LCS[i,j] = 1+LCS[i+1,j+1]
On the other hand, if s 1 [i] != s 2 [j] :
LCS[i,n 2 – 1] = 0 ∀i
Algorithm
Since the optimal substructure property is clear from the recursive
formulation of the solution, the main decision in the algorithm is in
deciding the sequence in which to calculate the values. That
decision is primarily driven by the order in which the values become
available. For example, to compute LCS[0,0] , we would need
values such as LCS[1,1] which we would not have. Therefore, the
correct sequence is to start with larger values of i and j.
We observe that this algorithm uses O(n 1 n 2 ) space and takes O(n 1
n 2 ) time.
The base case of this can be established as T(1,j) = C(1,j) for all
values of j .
Proof of Optimality
We can easily prove using contradiction that if T(i,j) is optimal, then
the constituent T(i-1,j) values must be optimal as well.
Algorithm
// Initialize
// Say we use 1 indexing, so ignore 0-index values
double[][] T = new double[n+1][k+1];
Each entry T(i,j) can be
// Base case computed in O(k) time.
for j = 1 to k
T[1][j] = C[1][j] Since there are a total of
O(nk) values, the overall
for i = 2 to n algorithm takes O(nk 2 )
for j = 1 to k time. The same can also be
T[i][j] = Infinity // Initialize to high value
for h = 1 to k such that h != j observed from the three
T[i][j] = min(T[i][j],T[i-1][h] + C[i][j]) nested loops in the
algorithm .
(w i , d i , h i )
(d i , w i , h i )
(d i , h i , w i )
(h i , w i , d i )
(h i , d i , w i )
Algorithm
The algorithm operates simply by generating all MH values in O(n 2
) time, and then taking the maximum over all values of MH(i) for all
values of i . Therefore, the entire algorithm runs in O(n 2 ) time .
Recursive Formulation
We can construct a subset with a sum of m’ from numbers selected
from [a 1 , a 2 … a j+1 ] in two different ways. We either use a j+1
or we don’t .
B(j+1,m') = max {B(j,m'-a j+1 ), B(j,m')}
Solution
When we traverse an undirected graph using DFS, we can only
encounter tree edges or back edges. It is not possible to see cross
edges or forward edges, because of the very nature of DFS. If were
to come across a cross edge (u,v), where the vertex v has already
been discovered, then that means that we would have traversed this
edge from v and (v,u) would have become a tree edge. Similarly if
(u,v) was classified as a forward edge where the vertex v has already
been discovered, then also, we would have traversed this edge in the
other direction (v,u) as a tree edge.
In the breadth first search traversal of an undirected graph, we can
encounter tree edges, back edges and also cross edges. However, we
cannot encounter any forward edges because due to the nature of
BFS, if an edge (u,v) was a forward edge, then v would be
discovered first through u , and therefore (u,v) would have become a
tree (discovery) edge.
In the case of directed graphs, all edges are possible, as the edges
cannot be traversed in both directions. Specifically, in case of DFS
on directed graphs, we can encounter tree edges, back edges, cross
edges as well as forward edges.
In the case of BFS on directed graphs, we can encounter tree edges,
back edges and cross edges. However, we still cannot encounter
forward edges for the same reason as described earlier, that if an
edge (u,v) was classified as a forward edge, then that means that
vertex v would have been discovered directly through u , and not
through a descendent of u .
Solution
Suppose we are given a directed acyclic graph G(V,E) , with an edge
(u,v) where u comes before v . We observe from the previous
question that because G is a DAG, there can be no back edges in G .
a. In a DFS traversal of G , if node u is visited before
node v , then node u must be an ancestor of node v
since there are no back edges. Therefore, there
must be at least one directed path going from node
u to node v that will have (u,v) in topological
order. QED
Consider that vertices can are white (unvisited), gray (pushed on the
stack), or black (popped off the stack). Vertices will become black
in reverse topological order. So to sort in topological order fill in an
array in reverse order from the end with the nodes as they become
marked black (popped off the stack).
For each node in the solution graph, we can derive both upper and
lower bounds on the assignment cost.
The upper bound can be a trivial assignment.
To derive a lower bound, we observe that:
Each job must be done – so if we add minimum
cost per job, then that must be minimum cost
Each person must do a job – so if we add minimum
cost per resource, then that must be minimum cost
Taking the maximum of these two minimums, is a
good “lower bound”.
Solutio n
Such a graph can be constructed as follows.
The description below assumes that the additions are 1 indexed, and
modulo in 5 , that is, 5 + 1 = 1 , etc. For every <v i , v i+1 , v i+2 >
in C 5 , add two more vertices x i and y i , such that v i , v i+1 , v i+2 ,
x i and y i also comprise a C 5 .
We can observe that the graph constructed so far, can also be colored
using 3 colors.
Now, we add 20 more “z” vertices. Each vertex is connected to one
of x i and one of y j such that i ≠ j .
[2] If the data about the rectangles is truly random, then we can pick either axis, of
course.
[3] This problem is found on many internet forums, and was apparently also used in a
Google job interview. The problem can also be posed in the form of cell phone drop
testing, by replacing eggs with cell phones, etc.