Contains solutions for problems from Elements of Programming Interviews and Cracking the Coding Interview books and other problems from other sources
# Name | T(n) | Examples |
---|---|---|
Constant | T(n) = O(1) | max(n, 100) |
Logarithm | T(n) = O(logn) | Binary search |
Linear | T(n) = O(n) | Smallest item |
Superlinear | T(n) = O(nlogn) | Mergesort |
Quadratic | T(n) = O(n^2) | Selection sort |
Cubic | T(n) = O(n^3) | ???? |
Exponential | T(n) = O(c^n) | All subsets |
Factorial | T(n) = O(n!) | Permutation |
1 << log(n) << n << nlogn << n^2 << n3 << 2^n << n!
- There are n! permutations of an n-element set
- There are 2^n subsets of an n-element set (because each element will be in or not in the set)
- There are n(n+1)/2 contiguous substrings in a string of length n
- There are n(n-1)/2 possible subtrees in a binary search tree
- The number of subsets of size k of a set of size n is n!/k!*(n-k)!
- This is also known as combinations
- ask questions to clarify ambiguity
- try out a small example(s) to explore possible algorithms
- identify needed data structures
- design an algorithm
- write pseudo code first (let interview know)
- write code at moderate pace
- stub out secondary concerns (swapping two numbers)
- test code and carefully fix mistakes
# bits | Value | Bytes |
---|---|---|
7 | 128 | |
8 | 256 | |
10 | 1024 | 1K |
16 | 65,536 | 64K |
20 | 1,048,536 | 1MB |
30 | 1,073,741,824 | 1GB |
32 | 4,294,967,296 | 4GB |
40 | 1,099,511,627,776 | 1TB |
Bytes | Name | Name |
---|---|---|
1,000 | 1KB | Thousand |
1,000,000 | 1MB | Million |
1,000,000,000 | 1GB | Billion |
1,000,000,000,000 | 1TB | Trillion |
1,000,000,000,000,000 | 1PB | Quadrillion |
Letters | Integer Value |
---|---|
A-Z | 65-90 |
a-z | 97-122 |
Category | Time (ns) | Time (us) | Time (ms) | Relative |
---|---|---|---|---|
L1 cache reference 0.5 ns | ||||
Branch mispredict | 5 ns | |||
L2 cache reference | 7 ns | 14x L1 cache | ||
Mutex lock/unlock | 25 ns | |||
Main memory reference | 100 ns | 20x L2 cache, 200x L1 cache | ||
Compress 1K bytes with Zippy | 3,000 ns | 3 us | ||
Send 1K bytes over 1 Gbps network | 10,000 ns | 10 us | ||
Read 4K randomly from SSD* | 150,000 ns | 150 us | ~1GB/sec SSD | |
Read 1 MB sequentially from memory | 250,000 ns | 250 us | ||
Round trip within same datacenter | 500,000 ns | 500 us | ||
Read 1 MB sequentially from SSD* | 1,000,000 ns | 1,000 us | 1 ms | ~1GB/sec SSD, 4X memory |
Disk seek | 10,000,000 ns | 10,000 us | 10 ms | 20x datacenter roundtrip |
Read 1 MB sequentially from disk | 20,000,000 ns | 20,000 us | 20 ms | 80x memory, 20X SSD |
Internet CA<->NY | 40,000,000 ns | 40,000 us | 40 ms | |
Internet CA<->Netherlands | 150,000,000 ns | 150,000 us | 150 ms |
Name | Time |
---|---|
nanosecond | 1 ns = 10^-9 seconds |
microsecond | 1 us = 10^-6 seconds = 1,000 ns |
millisecond | 1 ms = 10^-3 seconds = 1,000 us = 1,000,000 ns |
public static String toBase(int n, int base) {
if (base < 2 || base > 10) {
return "-1";
}
String result = "";
while (n > 0) {
result = (n % base) + result;
n = n / base;
}
return result;
}
- x & ~(x - 1) isolates the lowest bit (0100100 ==> 0000100)
- Right propagate right most bit
- x & (x - 1) changes the rightmost 1 bit to 0 (0100100 ==> 0100000)
- Use to count # of bits, detect if n is power of 2
- Integer.parseInt("01100110",2); // to parse an binary string into an integer
- XOR has the property of being associative (grouping) and commutative (ordering)
- Permutation - rearrangement of letters in specific order.
- Anagram - rearranging the letters of a word to produce a new word. i.e listen and silent
- Palindrome - a word which read the same backward or forward. i.e civic
public static List<String> generateSubStrings(String str) {
List<String> result = new ArrayList<>();
if (str == null) {
return result;
}
// two for loops to build all the possible substrings
for (int i = 0; i < str.length(); i++) {
for (int j = i; j < str.length(); j++) {
// now print out the substring from i to j
String subStr = "";
for (int k = i; k <= j; k++) {
subStr += str.charAt(k);
}
result.add(subStr);
}
}
return result;
}
public static List<String> generateSubsequences(String str) {
List<String> result = new ArrayList<>();
if (str == null || str.length() == 0) {
return result;
}
double numSequences = Math.pow(2, str.length());
for (int i = 0; i < numSequences; i++) {
String subSeqStr = "";
int index = 0;
int temp = i;
while (temp > 0) {
if ((temp & 1) == 1) {
subSeqStr += str.charAt(index);
}
index++;
temp = temp >> 1;
}
result.add(subSeqStr);
}
return result;
}
// using recursion
private static void recursionApproach(String remaining, String soFar) {
if (remaining.isEmpty()) {
collector.add(soFar);
return;
}
// include first character
recursionApproach(remaining.substring(1), soFar + remaining.charAt(0));
// don't include first character
recursionApproach(remaining.substring(1), soFar);
}
- Every number can be decomposed into a product of primes
- All non-prime numbers are divisible by a primer number
- All prime numbers are odd
- Heap is a binary tree where every node holds a value that is at least as large as the values in all children.
- Top or max n items requires min-priority queue because we want to replace the smallest element with a bigger element
- Bottom or min n items requires a max priority queue because we want to replace the largest element with a smaller element
- Level - Defined as 1+ the number connections between a node and the root. Root starts at level 1, meaning the level starts at value of 1 and it goes down from top to bottom. Level = depth + 1
- Height - number of edges from a node to the deepest leaf. Height of furthest eaf node is 0. Goes from bottom (0) to top (n). Height of a tree is the height of the root.
- Depth - number of edges from root to node. Goes from top to bottom and starts at 0.
- Height and depth should move inversely.
Property | Value |
---|---|
Branches | N -1 |
# of nodes | N = 2^(H+1) -1 |
Height | log(N+1) - 1 |
# leaf nodes | 2^H |
In a perfect binary tree, almost exactly half of the nodes are leaves and almost exactly half are internal nodes.
public static void preOrder(Node node) {
if (node == null) return
System.out.println(node.value);
preOrder(node.left);
preOrder(node.right);
}
public static void inorder(Node node) {
if (node == null) return
inorder(node.left);
System.out.println(node.value);
inorder(node.right);
}
public static void postOrder(Node node) {
if (node == null) return
postOrder(node.left);
postOrder(node.right);
System.out.println(node.value);
}
public static void bfs(Node node) {
Queue<Node> queue = new LinkedList();
queue.add(node);
while (!queue.isEmpty()) {
Node tmpNode = queue.poll();
// perform some processing
if (tmpNode.left != null) {
queue.add(tmpNode.next);
}
if (tmpNode.right != null) {
queue.add(tmpNode.right);
}
}
}
- Classify whether graph is directed or undirected
- Whether each edge has different cost or weight
- BFS -> for shortest path
- DFS -> exhaust search
- For BFS, use the parent map of (child -> parent) to maintain the path such that we can build the path at the end.
- For DFS, use a list to track the path, but make sure to back-track or remove the last one when pop up to parent node
- Maintain a visited set
class Graph {
private List<Node> vertices;
private Map<Node, List<Edge>> adjacenyList;
}
class Edge {
private Node sourceNode;
private Node desetNode;
private int weight;
}
- Identify base case and return appropriate value
- compute the left side ** Take appropriate action based on result: either stop recursion or continue
- compute the right side ** Take appropriate action based on result: either stop recursion or continue
- Now take action based on the result of both left and right side
- Formulate the answer as a recurrence relation or recursive algorithm
- Ensure the number of different parameter values taken on by recurrence is bounded
- Specify the order of evaluation for the recurrence so partial results are available when needed
bool solve(conf) {
if (no more choices) { // base case
return;
}
for (all available choices) {
make a choice;
ok = solve(conf with selected choice);
if (ok) {
return true;
}
unmake choice
}
return false;
}
- negative numbers, sorted, duplicates
public static int binarySearch(int[] input, int val) {
int left = 0;
int right = input.length - 1;
while (left <= right) {
// there are 2 ways to calculate the mid-index
// the first one is more safe because no overflow
// the second may run into overflow
int mid = left + (right - left) / 2;
//int mid = (left + right)/2;
if (input[mid] == val) {
return mid;
}
if (input[mid] < val) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}