Bottlenecks
Unnecessary work
Duplicated work
Bottlenecks usually occur in two ways:
Prompt
Given an array of distinct integer values, count the number of pairs of integers that have a difference of k
Input
array = [1, 7, 5, 9, 2, 12, 3] k = 2
Expected output
4 ([1, 3], [3, 5], [5, 7], [7, 9])
Brute force approach would be to iterate through the array. For every index, search for a number that is k offset from the value of the current index.
If we sort the array first, we could find the match for the value at the given index using a binary search.
Optimal solution, then, would be to use a hash table - add values of the array to a hash table and iterate through the array checking if valueofcurrent_index + k or valueofcurrent_index - k has already been added to the hash table.
Prompt
Print all the possible solutions to the equation a3 + b3 = c3 + d3 where a, b, c, and d are integers between 1 and 1000
Brute force
n = 1000
for a from 1 to n
for b from 1 to n
for c from 1 to n
for d from 1 to n
if a^3 + b^3 = c^3 + d^3
print a, b, c, d
The runtime complexity of this approach is O(N)4
By the time we get to the for loop for d, we know there's only one valid value for d, so we can just do math to figure it out rather than iterating through 1000 possibilities because d = the cube root of a3 + b3 - c3
...
d = pow(a^3 + b^3 - c^3, 1/3) // will round to int
if a^3 + b^3 = c^3 + d^3 // validate it works
print a, b, c, d
This reduces runtime complexity to O(N)3
Working with the same problem from unnecessary work:
You could create a list of (c, d) pairs once and when there's an (a, b) pair, find the matches within the (c, d) list. We can locate these easily if we store them in a hash table with the sum as the key.
n = 1000
for c from 1 to n
for d from 1 to n
result = c^3 + d^3
append (c, d) to list at value map[result]
for a from 1 to n
for b from 1 to n
result = a^3 + b^3
list = map.get(result)
for each pair in list
print a, b, pair
One better: Map all of (c, d) pairs will be the same as (a, b) pairs, so we can use the map directly rather than re-computing the pairs!
n = 1000
for c from 1 to n
for d from 1 to n
result = c^3 + d^3
append(c, d) to list at value map[result]
for each result, list in map
for each pair1 in list
for each pair2 in list
print pair1, pair2
Now the runtime complexity is O(N2)
When given a problem, try working it through intuitively on a real example. Bigger examples usually are easier. Think about how you solved it and try to reverse-engineer your solution. Make a note of any mental jumps you took in the solving of the problem, such as ignoring values based on a given critera.
Take a problem, remove or modify a constraint to see how to solve a simpler version of the problem. Then try to adapt the solution to a more complex version.
Prompt
A ransom note is formed by cutting words out of a magazine to form a new sentence. How do you figure out if a ransome note can be formed from a given magazine?
Technique
To simplify, modify so you're cutting characters out of a magazine instead of whole words - you can then solve by creating an array and counting the number of characters -> each index represents one letter of the alphabet. Count the number of times each character appears in the note, then check the magazine for those same characters.
To generalize, we create a hash table of words and their frequencies.
Solve the problem for the base case first (e.g., n = 1) and then build from there, trying to build more complex cases from prior solutions
Prompt
Design an algorithm to print all permutations of a string. For simplicity, assume uniqueness
test input
abcdefg
case "a" -> { "a" }
case "ab" -> { "ab", "ba" }
case "abc" -> ???
How can we generate "abc" from "ab"?
P("abc") = insert "c" into all locations of all strings in { "ab", "ba" }
P("abc") = merge ({ "cab", "acb", "abc" }, { "abc", "bca", "bac" })
P("abc") = { "cab", "acb", "abc", "cba", "bca", "bac" }
Generate all permutations by chopping off the last character, generating all permutations of the string without it, iterating through thtat list, and inserting the chopped off character at every location of the string
Base Case & Build algorithms often lend themselves to natural recursive algorithms
It's a little hacky, but effective. Go through a list of data structures and try to apply each one. The problem may be trivial once you find the right data structure to approach the problem with.
Prompt
Numbers are randomly generated and stored in an expanding array - how do you keep track of the median?
Thought process
Best runtime you can imagine a solution to a problem having. This is based on inputs and outpus and has no particular connectiont to a specific algorithm.
Admit this to your interviwer. They're trying to gauge your problem solving and they can't do that if you already know how to solve the problem.
Ultimately, it's up to you and you should code in the language you're most comfortable coding in.
The solution works on expected and unexpected inputs
Operates with good space and time complexity. Be mindful of constants that don't matter in Big O but may matter in an application
Another developer should be able to read your code and understand what it's doing. You should comment your code where necessary. Implementation should be understandable.
Code should be able to adapt to change
Prompt
Write a function to add two simple mathematical expressions in the form Axa + Bxb + ... (coefficients and exponents can be any positive or negative real number) without employing the use of string parsing. Use whatever data structure you want to hold the expressions.
Storing the expression as an array of doubles where kth element corresponds with coefficient of the xk term in the expression.
Why it's bad
Does not support negative or non-integer exponents. Requires an array of the length of the largest exponent.
Store expression as 2 arrays - coefficients and exponents
double [] coef1, double[] exp1, double[] coef2, double[] exp2
This approach doesn't have the same restriction the previous one did as the index is no longer directly tied to any meaning for the values in the array. However, it's still a bit messy and complicated because you need to keep track of two arrays for a single expression.
Create your own data structure!
struct ExprTerm {
double coefficient;
double exponent;
}
Now you can have an array of an expression rather than separate arrays.
If you need to do something with different input that gets manipulated the same way (i.e., converting numbers from binary as well as converting numbers from hexadecimal), build a function that can handle different instances
Break functions apart!
Allow for more general input when possible rather than the expected input. UNLESS GENERALIZING WILL ADD SIGNIFICANT COMPLEXITY
It's good practice to validate input through the use of assert and if statements. If error checking is much more than a quick if statement, it might be best to leave space where the error check would go and indicate to the interviewer that you would write them when finished