Algorithms Flashcards

1
Q

Describe Dijkstra’s Algorithm

A

Start with initial node, labeling above every node in the graph the distance from start vertex to that node. If it doesn’t directly connect to a vertex, the distance is currently infinity.

Check all vertices that have not been visited yet and select the one with the shortest current distance. (This means you do have to iterate through the list of vertices and check their distances each iteration.) Use it as an intermediary between the other vertices it is connected to. “Relax” the length of the vertices using the currently selected vertex as an intermediary. The formula is:
If (d[u] + c[u, v] < d[v])
d[v] = d[u] + c[u, v]
d[u] is the distance from the initial vertex to the currently selected vertex. c[u, v] is the distance from vertex u (selected) to vertex v (vertex we are calculating). Repeat “relaxation” for all vertexes directly touching the currently selected vertex.

Could be O(n^2) if every vertex was connected to every other vertex, in the worst case.
May fail and give a wrong answer if there are negative edges because a negative number can yield a better distance for a vertex, but since the vertex has already been selected, it won’t change it to the better value.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Describe the Binary Search Algorithm

A

Must be a sorted list. Check middle value if it is greater/less than the value we’re searching for. If it is greater, discard mid to high value and repeat for left side of array. Repeat until mid value is the searched for value/only value left. O(logn) time.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Describe BFS Algorithm

A

Add initial node to queue and add its value to the order list. Start the while loop. Pop it from the queue. Add all adjacent nodes that are connected to the current node to the queue and add them to the ordered list ONLY IF THEY HAVENT ALREADY BEEN EXPLORED (Don’t need to worry about most of the time). Repeat this process, popping the most recent node off the queue and adding adjacent nodes that haven’t been explored yet to the queue and the list.
Create a BFS spanning tree, adding solid lines to create the tree and dotted lines to adjacencies that have already been explored (cross-edges). Edges that go backward a back edges I believe.
Many possible outcomes because there is no specific order you have to go in when adding adjacent nodes to the queue.

O(n) time where n is the number of nodes in the tree and assuming there are no duplicate children. O(n) space for the queue to hold potentially all the nodes in the tree. O(n^2) time is possible if all nodes are connected to every other node.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Describe DFS Algorithm

A

Use a stack. See if initial node has an adjacent node. If it does, add initial node to the stack and the node it was connected to is now the current node. Repeat this until the current node does not have a node adjacent to it. Pop a node off the stack and check for another adjacent node that has not been visited. If there is, put it back on the stack and explore down that path. If not, pop the next one off the stack and make that the current node. Recursive solution also possible here.
Make DFS spanning tree, making back-edges to additional adjacencies that have already been visited.
Many possible outcomes here as well because you can choose different adjacencies.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Describe the Merge Sort Algorithm

A

Divide and conquer algorithm that separates a larger array into subarrays until each subarray has only 1 element (which is technically sorted). They then recursively merge the subarrays together.
The merge works by having two lists to sort and a third list to fill with the values of the two smaller lists. You keep track of the indexes of the two smaller lists and compare the list values at those indexes. Keep track of the third list’s index as well.
Merge(array A, array B):
Array C[A.size() + B.size()];
indexA = 0;
indexB = 0;
indexC = 0;
While (indexC < C.size()):
If (indexA < A.size() && A[indexA] < B[indexB]) { // && should short circuit so no OOB
C[indexC] = A[indexA];
indexC++; indexA++;
} else {
C[indexC] = B[indexB];
indexC++; indexA++;
}
Return C;
All of the recursions will be O(n), O(logn) times, so O(nlogn). If we are talking about merging itself without starting with an initial list, use O(n+m), where the two letters are the sizes of the two lists being compared.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Describe the Quick Sort Algorithm

A

Works on the idea that one of the numbers are already in the correct sorted place in the array, so add infinity to the end of the list (or after high index of place in array you want to be sorted) because the infinity will be in the correct spot.
Low value is lower index where you want the sorting to take place. High index is where you added infinity. The value at the starting low index is the number we’re going to place in the correct index relative to the other numbers we are sorting. All values below it will be lower and all values after will be higher. This is called the pivot. Variable I will start at low index. Variable j will start at high index. Increment I until you find a value greater than the pivot. Decrement j until you find a value lower than the pivot. If i is less than j, swap values. Continue to do this while index I is less than index j. When they cross over and index I becomes greater than index j, swap value at the low/starting index (which value is the pivot) with the value at index j. The pivot value is now in the correct spot and that quicksort was completed. Return j so the next iteration knows where the new high and low value will be. This function is called “partition”.
The returned j from the partition method is the new “high” index for the lower portion of the array, and j+1 is the “low” index for the right portion of the array. (Update: j doesn’t seem to work for all cases as the lower portion’s high. Use j-1 as the high and j+1 as the low for the right portion.) Run quicksort using the original low value for low and j as the new high, and also on the right portion using j+1 for the low and the original high as the right portion’s high
Quicksort(low, high):
If (low < high):
J = partition(low, high)
Quicksort(low, j)
Quicksort(j+1, high)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Describe Prim’s Algorithm

A

Greedy algorithm for finding a minimum cost spanning tree. Given a graph, start with the edge with the least cost. Then using the two vertices that are included in this initial tree, find the minimum cost edge stemming from those and connect the vertices at the end of that edge. The next edge will always be the lowest cost edge connected to the current tree we’re making that leads to a vertices that has not yet been visited.
SPANNING TREES ARE ONLY POSSIBLE WITH GRAPHS THAT ARE CONNECTED. NO ISLANDS.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Describe Kruskal’s Algorithm

A

This always adds the next lowest cost edge to the tree, but only if it does not form a cycle. In other words (I’m pretty sure), we just don’t add an edge that leads to a vertices that has already been visited. To find the next lowest cost edge each time is more expensive though. The algorithm ends up being O(v*e), or the number of edges times the number of vertices. There is an improvement though.
A min heap will keep those values sorted, so searching for those values will only be logn, for a total of O(nlogn) time. (A min heap is a binary tree that has the minimum value as the top node. A max heap has the maximum value as the top node. The data structure allows us to easily insert things into the heap while keeping everything sorted, and when we pop the top value, we can adjust the rest of the tree to maintain a sorted order as well.)
KRUSKAL’S CAN FIND MINIMUM SPANNING TREE FOR INDIVIDUAL COMPONENTS IF THE GRAPH IS NOT CONNECTED, BUT NOT A MINIMUM SPANNING TREE FOR THE WHOLE GRAPH.
A minimum cost spanning tree should have n-1 edges, where n is the number of vertices in the graph.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Describe the Floyd Warshall Algorithm

A

Make an adjacency matrix showing the path lengths to each vertex to every other vertex. Make new adjacency matrix, plugging in new values if using the first vertex as an intermediary yields a shorter path. (Note that vertices that do that have a path to another vertices has infinity as the length, so if the first vertex connects the two, the length will inevitably be smaller than the original matrix.) Continue to do this for all the other vertices, finding the smallest values for the adjacency matrix.
Notice that the initial matrix will have values of zero for the diagonal from top left to bottom right. This is because a length from vertex 1 to vertex 1 is zero. Same for 2-2, 3-3, etc. Also, when we are making a new matrix, using 1, for example, as the intermediary vertex, the row and column for 1 will maintain the same values as the initial matrix. When we move on and use vertex 2 as the intermediary, the row and column for 2 will be the same as the previous matrix we just made when we used 1 as the intermediary.
Always compare new matrices that we’re making to the matrix we just made before. This will maintain the smallest values.
This takes 3 for loops nested inside each other. O(n^3) time as each for loops is n time. O(n^3) space as well since each matrix is n^2 and there are n matrices that need to be made, so n^3.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What is the difference between Kruskal’s, Prim’s, Dijkstra’s, and Floyd Warshall Algorithms? Also Bellman Ford?

A

Kruskal’s and Prims are the same in that they are finding the minimum cost spanning tree, or in other words, they are recreating the given graph but attempting to include only the edges that will make the cost between nodes the smallest. There are n - 1 edges in a minimum spanning tree, where n is the number of nodes.

Prim’s and Kruskal’s are different because Kruskal’s picks whatever is the next shortest edge in the graph without making a cycle. Prim’s picks the shortest edge that is attached to the current tree. Prim’s starts with the lowest cost edge and sprouts from there. Since Kruskal’s has to search the whole graph for the next lowest cost edge each iteration, it will take more time, but Prim’s might be a worse solution.

Dijkstra’s is trying to find the least distance from a starting node to every other node. It uses intermediate nodes to reduce distances to other nodes by “relaxing” the distances. It is like Prim’s in that it “branches” out from a starting point, using the next lowest distance node as an intermediary between the other nodes.

Floyd Warshall uses Dynamic Programming (adjacency matrices) to essentially perform dijkstra’s on every node to every other node.

Bellman Ford iterates through all edges n-1 times, relaxing nodes with those edges. If done the nth time and there was a change, this means there was a negatively weighted cycle in the graph.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What is Dynamic Programming?

A

When you test all possible solutions at a given state and choose the best one.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Describe the Bellman Ford Algorithm

A

Make a list of every edge in the graph. Then start at the chosen starting node and set all other node distances to infinity. Iterate through every edge in the list and relax the edges. Iterate through every edge in the list again, continuing to relax edges with updated values. Do n-1 sets of iterations through the graph, or I suppose until no change is made through a round of relaxing all the edges. This allows you to work with graphs with negative edges. (When writing the algorithm, don’t stop until n - 1 times.)

Worst case O(n^2) normally, but if every node is connected to every other node, it will be O(n^3)

DOES NOT WORK IF THERE IS A NEGATIVE WEIGHT CYCLE. There will always be a shorter path being created within the cycle forever. A positive cycle is fine because it will produce a higher cost, so the algorithm won’t switch from the current value. You can use this algorithm to check if there is a negative weight cycle by going through the edges an nth time. If something changes, you know there was a negatively weighted cycle.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Describe the Insertion Sort Algorithm

A

Index zero is the “sorted” portion of the array, and everything to the right of it is unsorted. Store the value of index 1 in a variable and check to see if the previous value is greater than it. If it is, shift it up 1 index to fill the hole and put the value stored in the variable at index zero. Now those two are sorted. The next value will do the same thing, shifting previous values up if they are greater until we’ve reached index zero or found a value less than the current variable we are trying to place.

Best case is O(n) if array is already sorted. Worst case is O(n^2) if the values are reversely sorted I believe.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Describe the Selection Sort Algorithm

A

Make a second array of the same size. Iterate through all values in array we are trying to sort and store the minimum value. Also keep track of the index it was at. Push that value onto the second array and set the value in the first array at the stored index to infinity so it will never be chosen again. Do this n times until all the values from the first array have been added to the new array in ascending order. O(n^2) time.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

What is a binary search tree? What are the functions you need to define for a BST implementation? How do you implement those functions?

A

A binary search tree is a tree data structure where each node in the tree satisfies this property: All children on the left of a node have a value less than the current node’s value and all children on the right have a value greater than or equal to the current node’s value.

To implement a BST, you need insert, remove, and contain functions.

// Insert
To insert, simply check if the value is less than the current node’s value or if it’s greater than/equal to the current node’s value. This will decide if it’s going right or left.
If the left/right node is currently null, make the node a new BST with the value we’re trying to insert. If the left/right node is not null, just call the same insert function on that child node and it will continue down the tree until there is a place to insert it.

public BST insert(int value) {
if (this.value > value) { // Going to the left
if (this.left == null) {
this.left = new BST(value);
} else {
this.left.insert(value);
}
} else { // Going to the right
if (this.right == null) {
this.right = new BST(value);
} else {
this.right.insert(value);
}
}
return this;
}

// Contains
Check if the value we’re searching for matches the current node’s value. If it does, return true. If the current node’s value is greater than the value we’re looking for, return the value that comes from calling contains(value) again on the left child of the current node. If that child doesn’t exist, return false. Vice versa with the right child if the value we’re looking for is greater than the current node’s value

public boolean contains(int value) {
if (this.value == value) {
return true;
}

  else if (this.value > value) {   // Search left
    if (this.left != null) {
      return this.left.contains(value);
    } else {
      return false;
    }
  } else {   // Search right
    if (this.right != null) {
      return this.right.contains(value);
    } else {
      return false;
    }
  }
}

// Remove
This one is tricky. You traverse the graph the same way as contains until you find the value. There are 4 cases when this happens: left is null and right is not, right is null and left is not, both are null, and neither are null.

If one child is null and the other is not, just set the current node’s value and children equal to the non-null child. It will be as if the child just scooted up in the tree and the value we we’re searching for will be removed.

If the value has no children, return null, which indicates to the parent that we should just set that child to null, removing it.

If the node we’re removing has two children, we replace it with the lowest value on the right. This means jumping to the right child and then traversing children on the left until the left child is null. Once our helper function finds that value, we replace the current node’s value with that value and call remove again to remove it from the bottom of the tree. This process will ultimately move it from the bottom of the tree to the place our current node is, successfully removing the desired value and placing the appropriate value in its place.

Remember, whenever calling remove recursively, we need to set it equal to a BST. If that BST returns null, we need to set the child we called remove on to null.

public BST remove(int value) {
if (this.value == value) { // found it
if (this.left != null && this.right == null) {
BST left = this.left.left;
BST right = this.left.right;
this.value = this.left.value;
this.left = left;
this.right = right;
} else if (this.left == null && this.right != null) {
BST left = this.right.left;
BST right = this.right.right;
this.value = this.right.value;
this.left = left;
this.right = right;
} else if (this.left == null && this.right == null) { // Return null tells parents to set this child to null
return null;
} else {
int newValue = this.right.getSmallestFromRight();
this.value = newValue;
BST erase = this.right.remove(newValue);
if (erase == null) {
this.right = null;
}
}
}

  if (this.value > value) {   // Search left
    if (this.left != null) {
      BST erase = this.left.remove(value);
      if (erase == null) {
        this.left = null;
      }
    }
  } else {   // Search right
    if (this.right != null) {
      BST erase = this.right.remove(value);
      if (erase == null) {
        this.right = null;
      }
    }
  }
  // Do not edit the return statement of this method.
  return this;
}

public int getSmallestFromRight() {
  if (this.left == null) { 
    return this.value; 
  }
  else { return this.left.getSmallestFromRight(); }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

How do you validate a BST?

A

Take the BST given to you and pass it through a validate function with a minimum and maximum of -inf and inf.

return validate(tree, -inf, inf);

Each node traversed will have a minimum and maximum value that will make the tree valid, so the root node can be any number with infinite values as the parameters.

The validate function will return false if the node’s value is outside the boundaries of the min and max. If it falls within the boundaries, we can move on and check the child nodes if they are not null.

When checking the left child, pass through the validate function and maintain the current minimum value as the new min. The new maximum value will be the current node’s value - 1 since all nodes to the left of it should be strictly less than.

When checking the right child, pass through the validate function and maintain the current maximum value as the new max. The new minimum value will be the current node’s value since it must be greater than or equal to the current node’s value.

The validate function will return false if it finds an invalid value, so whenever validating a child, store the boolean return value. If it’s false, return false. If not, don’t do anything and just continue. At the end of the validate function, return true. This setup will jump up the layers of recursion and return false as soon as an invalid value is found. Otherwise, it will traverse the whole tree and eventually return true.

public static boolean validateBst(BST tree) {
return validate(tree, -Integer.MAX_VALUE, Integer.MAX_VALUE);
}

public static boolean validate(BST tree, int min, int max) {
if (tree.value > max || tree.value < min) {
return false;
}
if (tree.left != null) {
boolean left = validate(tree.left, min, tree.value - 1);
if (left == false) return false;
}
if (tree.right != null) {
boolean right = validate(tree.right, tree.value, max);
if (right == false) return false;
}
return true;
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

How do you perform traversals of a binary search tree? InOrder? PreOrder? PostOrder?

A

Do a depth first search in all cases, so the recursive function should call the traversal on the left child if not null and then the right child if its not null. At some point during those calls, the current node’s value should be added to the array that holds the order of traversal.

Inorder: the current node’s value should be added to the array after the left child’s traversal returns and before the right child’s traversal call.

PreOrder: the current node’s value should be added to the array at the start of the traversal function, before the left child is traversed.

PostOrder: the current node’s value should be added to the array after the right child has been traversed at the end of the traversal function.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

What is a min height BST? How would you create a min height BST out of a list of integers given a properly implemented BST insert function?

A

First, create a helper function that gets the mid index given a left and right index. (Get the difference of the indexes, cut the difference in half (floor value), and add that half to the left index to get the index in the middle.)

public static int getMidIndex(int left, int right) {
int dif = right - left;
int half = (int)Math.floor(dif/2);
return left + half;
}

Now for the actual function, store the mid index of the entire array given.

int midIndex = getMidIndex(0, array.size()-1);

Then create the root node from that index.

BST tree = new BST(array.get(midIndex));

Now we need a recursive function that will assign left and right children to a given node after passing in the appropriate index boundaries. The idea here is to add the median value from each subarray in order to get a BST of the smallest height.

The assign child function will take the original BST, a left index, right index, and the integer array. With these values we have 3 special cases and a normal case.

If the right index is less than the left index, return.

If the left and right indexes are the same, insert the value at that index to the tree.

If the right index minus the left index is 1, there are just 2 values there we need to add, so add the left index value and the right index value and return.

Otherwise, get the mid index using the helper function, insert the value at that index into the tree, then call the assignChild function again using new left and right boundaries. The first will be the current left index as the left index and midIndex - 1 as the right index. The other call with have midIndex + 1 as the left index and the current right index as the right index. This process of adding the mid value to the tree and then splitting the left and right indexes up effectively splits the array in half over and over until all the array values are added to the tree in that fashion. This will produce a min height BST.

public static BST minHeightBst(List<Integer> array) {
int midIndex = getMidIndex(0, array.size() - 1);
BST tree = new BST(array.get(midIndex));
assignChild(tree, 0, midIndex-1, array);
assignChild(tree, midIndex+1, array.size()-1, array);
return tree;
}</Integer>

public static void assignChild(BST tree, int left, int right, List<Integer> array) {
if (right < left) return;
if (right - left == 1) {
tree.insert(array.get(left));
tree.insert(array.get(right));
return;
}
if (right == left) {
tree.insert(array.get(left));
return;
}
int midIndex = getMidIndex(left, right);
tree.insert(array.get(midIndex));
assignChild(tree, left, midIndex-1, array);
assignChild(tree, midIndex+1, right, array);
}</Integer>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

How would you get the kth largest integer in a BST?

A

Do a depth first search going to the right and store the integers in an inOrder fashion. So your depth first search function will traverse the right child if it is not null, then add its value to the array, then traverse the left child if it is not null. After traversing all the nodes, the array will have the values sorted in descending order. You just need to return the value at the array at index k-1.

public int findKthLargestValueInBst(BST tree, int k) {
List<Integer> array = new ArrayList<>();
dfs(tree, array);
array.stream().forEach(n -> System.out.println(n));
return array.get(k-1);
}</Integer>

public void dfs(BST tree, List<Integer> array) {
if (tree.right != null) {
dfs(tree.right, array);
}
array.add(tree.value);
if (tree.left != null) {
dfs(tree.left, array);
}
}</Integer>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Given the preOrder traversal values of a BST, how would you recreate the BST?

A

Implement the BST insert function. Create a new BST out of the first value in the integer array, then loop through the rest of the array, inserting the values into the root of the BST.

class Program {
// This is an input class. Do not edit.
static class BST {
public int value;
public BST left = null;
public BST right = null;

public BST(int value) {
  this.value = value;
}

public BST insert(int value) {
  if (this.value > value) {
    if (this.left == null) {
      this.left = new BST(value);
    } else {
      this.left.insert(value);
    }
  }

  if (this.value <= value) {
    if (this.right == null) {
      this.right = new BST(value);
    } else {
      this.right.insert(value);
    }
  }
  return this;
}   }

public BST reconstructBst(ArrayList<Integer> preOrderTraversalValues) {
// Write your code here.
BST tree = new BST(preOrderTraversalValues.get(0));
for (int i = 1; i < preOrderTraversalValues.size(); ++i) {
tree.insert(preOrderTraversalValues.get(i));
}
return tree;
}</Integer>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

How do you invert a binary tree? (Note: this is not a binary search tree. Just a tree with two or less children per node.)

A

Just traverse the tree and for each node, switch the right and left children. Then continue the traversal on only non-null children. There are 3 cases each traversal:

If both children are non-null, swap them and traverse both.

If one child is null, set that to the non-null child and set the non-null child to null. Traverse the new non-null child. (This is 2 cases, either left is null or right is null and the other is not.)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

How do you find the largest diameter in a binary search tree?

A

The diameter of a binary search tree is the longest path in the tree, which doesn’t necessarily have to include the root node.

To get it, do a depth first search that returns the height of the node returning. So when you reach a leaf node with no children, return 1 because leaf nodes have height 1.

On parent nodes, record the heights of the children if they exist by calling the same DFS function recursively, and if one of the children don’t exist, just set their height to 0.

To find the appropriate height and diameter of that node, set the diameter to the height of the left child plus the height of the right child. To set the height, get max(rightChildHeight, leftChildHeight) plus 1. In other words, the current node’s height is either the left or right child’s height – whichever is higher – plus one.

After you have your height and diameter, check if the diameter is greater than the largest diameter. If it is, change the largest diameter to the current node’s diameter. Then return its height.

Remember to make the largest diameter value a variable within the class. Passing it through the function recursively isn’t going to work.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

How do you find a successor to a specified node in a binary tree?

A

Perform an inOrderDFS search (recursively search left child if it exists, add current value to array, search right child if it exists). Once you have the array filled with the values, go through the array and find the node value specified. Return a new node with the value after that.

24
Q

How do you tell if a binary tree is height balanced?

A

A height balanced binary tree is one where the left and right subtrees don’t have a height difference greater than 1.

Set a class variable “balanced” equal to true by default.

Do a depth first search on the given tree. If you reach a leaf node (no children), return 1 for the height. For all other nodes, do the same function for the left and right children whichever exists. If the difference between the heights returned from those is greater than 1, set “balanced” to false. Return the height of the current node, which is either the left or right child height – whichever is higher – plus one. Use Math.max(left, right) to get this value, then add 1. The original function should return the boolean “balanced”.

This is O(n) time complexity where n is the number of nodes in the tree. O(h) space complexity, where h is the height of the tree. This is because the values to store the left/right children heights will only be there until the function is returned, so only h amount of variables will be declared at a single time.

25
Q

Given an array of integers, how would you find the max sum of numbers where none of them or adjacent to each other?

A

You would make an empty array the size of the given array. Assign the first value in the new array the first value in the given array. Assign the second value in the new array either the first or second value in the given array, whichever is larger. Then, starting at index 2, go through the new array assigning it values based on this formula:

Math.max(maxSums[i-1], maxSums[i-2] + array[i]);

This will keep track of the largest values possible at the given index. Return the value at the last index to return the largest possible sum.

This solution is O(n) time and O(n) space, but it can be better if your max sums list is only 2 in length. Instead of keeping the unnecessary previous values, just keep updating the values so that only the 2 necessary values for getting the next number are stored. This is O(n) time and O(1) space, where n is the number of values in the given array.

26
Q

Given an amount to reach and a list of coin denominations, how would you return the amount of ways you could combine the coins to add up to the value? Unlimited coins of the given denominations are provided. Describe intuitively why this works.

A

If the amount is zero, return 1 because the only way to achieve that is to give no coins, which is technically a valid combination. Just no coins.

We want to make a new integer array that has the amount we are trying to reach + 1. Initialize all the amounts to zero except for index 0, initialize to 1. Each index in this array represents the amount of combinations we can use to reach the given value, so index 0 holds the amount of combinations possible to reach value 0, which is always 1. Index 5 will hold the number of possible combinations of given coins we can use to get $5, and so forth.

The “ways” array must be n+1 because it includes 0 and all the possible ways to achieve the given number n, so n + 1 indexes.

Make 2 nested for loops, the outer one looping through the coin denominations and the inner loop looping through the “ways” array. At each iteration, if the coin denomination is less than or equal to the amount n, ways[n] += ways[j - denoms[i]]; This will add all the possible combinations in a dynamic programming manner. The answer will be ways[n], since this will return the number of combinations possible to get the desired value n.

O(nd) time complexity, where n is the amount we’re trying to reach and d is the number of coin denominations given. O(n) space because the array to hold the different ways is n+1 long.

This process works because you’re discovering a remainder for each value. So if I’m looking at getting 6 dollars with a 4-dollar coin, we can subtract 4 from six (4 is less than/equal to 6 in the algorithm). Then we back track to index 2 in our array. Index 2 is all the ways we can create that remainder in order to reach 6, so we can add that to the amount of ways to reach 6 with 4 being the Segway to get there. So beginning with the absolute that there is only 1 way to make zero (using zero coins), we establish every other possibility out of this. Then further possibilities are used to establish other possibilities.

Update: Remember to make denoms the outer for-loop. You need to iterate over the ways array multiple times and apply each denomination to it. If you do it the other way around, you’re applying all coin denominations at each way index. This is going to add too many later on. For example:

denoms: [1, 5]

At value 5, you’re going to get 2 ways to make this. One 5 or five 1’s. When you include the 1 coin to value six, you’re adding 2 ways to it. Then when you include 5 to it, it’ll add one more way for a total of 3 ways to get to 6. This is incorrect! Because you applied the 5 coin to value 5, when the 1 was updating value 6, it included that addition. You’ve essentially counted 5 twice instead of just once for that iteration.

Perhaps this teaches us that in dynamic programming, we iterate through the data structure being manipulated multiple times. Maybe this isn’t always the case, but we’ll see.

27
Q

How would you find the minimum number of coins needed to reach a value, given the value and an array of available coins? Describe intuitively why this works.

A

Perform dynamic programming by making an array with n+1 elements, n being the value we’re trying to reach. Each index represents the minimum number of coins needed to reach that value, all the way up to the value we’re looking for. Initialize the array at index 0 to 0 and all other values to positive infinity.

Make two nested for loops, the outer loop going through the coin array and the inner loop going through the nums array we just made.

If the coin we’re at is less than or equal to the inner index amount (denoms[i] <= j), set the nums array at j to:

Math.min(nums[j], nums[j - denoms[i]] + 1)

This will keep the current value at nums[j] if its less or replace it with the lower amount by taking into account the current coin.

The answer will be nums[nums.length-1], or the last number in the nums array. If that number is still positive infinity, that means there is no way to get that value with the given coins.

Update: Remember the Math.min() command instead of just setting nums[i] = nums[i-denoms[j]] + 1; This would work if all the denoms were in order, but because they are not, a later solution could be better.

This is O(nd) time and O(n) space, n being the value we’re trying to reach and d being the number of coins given. O(nd) comes from the nested for loops, one going through the n length array and the other going through the coin array. The nums array is n length, so O(n) space.

public static int minNumberOfCoinsForChange(int n, int[] denoms) {
// Write your code here.
int[] ways = new int[n+1];
ways[0] = 0;
for (int i = 1; i < ways.length; ++i) {
ways[i] = 999999; // Represents infinity
}

for (int i = 0; i < denoms.length; ++i) {
  for (int j = 1; j < ways.length; ++j) {
    if (denoms[i] <= j) {
      int remainder = j - denoms[i];
      ways[j] = Math.min(ways[j], ways[remainder] + 1);
    }
  }
}

if (ways[n] == 999999) { return -1; }  // Amount not possible
return ways[n];   }

Like finding the different ways to reach a particular value, this method uses remainders in addition to the current coin we’re using to find out how we can reach the desired value. If the desired value is 6 and we are on a 4-dollar coin, we look back at index 2 to see the minimum number of coins it takes to make 2. If we take that number plus 1 (adding on the 4-dollar coin to make 6), this will be the minimum amount unless the current value is already smaller than that. Starting with the base case that value zero takes zero coins to produce and assuming the rest of the indexes start at infinity, we can find out the minimum number of coins for all other values stemming from this base case and adding 1.

28
Q

Given a rectangle with boxes n width and m height, how would you find out how many ways can you go from the top left of the rectangle down to the bottom right of the rectangle? You can only go right or down.

A

Use dynamic programming. The base cases here are the top row and left column, which are all 1. There is only a single way you can get to each of these boxes. From there, traverse from rec[1][1] down to rec[m-1][n-1]. Each box is equal to the box to its left plus the box above it, so:

rec[i][j] = rec[i-1][j] + rec[i][j-1];

To optimize the space complexity, you can just use 2 rows at a time. Whichever row is completed, make it the top row, make the left-most value 1 as this is the consistent base case. Then traverse through the row. You could also do this using to columns instead of rows. If you use whichever is shortest, you space complexity will be O(min(width, height)). Time complexity will be O(width*height).

29
Q

How do you find the minimum number of edit operations required to make two words the same?

A

Use dynamic programming. Make a 2d array of the length of the words + 1. One word represents the top and the other word represents the side. The +1 is so you can technically add an empty character to the beginning of both. That way, the position (0, 0) can be initialized to 0 as the base case and the proceeding positions on the first column and first row can simply increment from there:
“” y a b d
“” 0 1 2 3 4
a 1
b 2
c 3

After you have your initial 2d array, start at position (1, 1). There are 2 cases:

If (string1.charAt(i) == string2.charAt(j)) {
E[i][j] = E[i-1][j-1]
}

else {
E[i][j] = Math.min(Math.min(E[i-1][j], E[i-1][j-1]), E[i][j-1]) + 1;
}

If the strings are the same at those locations, there is no edit needed, so you take the diagonal up and to the left and set it as the value without adding anything to it. You’re essentially progressing through both words without adding an edit.

If the letters are not the same at those locations, check each of the three connecting boxes: left, top, and diagonal. Take whichever is the smallest value, add 1, and set it to that box.

When this process finishes, the bottom right box will hold the minimum number of edits for those two words.

This is O(nm) space complexity where n and m are the length of the two strings. It is O(nm) space complexity as well with the 2d array, but you can optimize this by initially making the base cases for either the top row or left-most column, whichever is smaller. For the top row filled out, make a second row and initialize the first value in the row to whatever iteration you’re on. Then fill in the boxes as usual. When you’re all done, make that row the new top row and make a new row and fill that in the same way. It works the same for the columns. Once you’re done with your last iteration, the final value you fill in will be your answer. The space complexity in this case will be O(min(n, m)).

30
Q

Describe the bubble sort algorithm.

A

Until no swaps take place, iterate through the array, and at each index, see if the current value is greater than the value above it. If it is, swap them and continue.

In the implementation, make a boolean “swap” and set it to true and have the while loop going while it is true. At the beginning of the while loop each time, set it to false and only set it to true when a swap occurs.

You can decrease the time it takes by initializing another int j before the while loop. In the for loop within the while loop, you’d typically do (int i = 0; i < array.length - 1; ++i)

Instead of array.length - 1, set j to 1 initially and do array.length - j. Before the while loop restarts, increment j. This is because each time bubble sort does an iteration, it is guaranteed to place the next greatest integer at the end where it belongs, so you don’t have to check an additional end value each iteration.

The space complexity is O(1) as we aren’t creating any additional data structures and the time complexity is O(n^2). This worst case is when the list of numbers is in reverse order. It can, however, do O(n) if the array is in sorted order already. It will only iterate one time because no swaps will occur.

31
Q

What does Kadane’s algorithm solve and how do you implement it?

A

Kadane’s algorithm solves the problem of finding the subarray that yields the highest sum. This handles negative numbers in the array as well. (A subarray is an array of integers that are all connected within the given array.)

We use dynamic programming to discover the largest sum. Note this does not give us the indexes but just the largest sum we can find from the subarrays.

Make an array of length 2 called sums. Set the first number to the first number of the given array. Also store an int “largest” as that same value so we can start keeping track of the largest value.

Loop through the given array one time. At each iteration, set sums[1] = Math.max(sums[0] + array[i], array[i]); This will add the previous largest value possible onto the current value, or if the current value is greater than what that would have been, it just stores that. If that sum is greater than the current largest value, change “largest” to that value. Then set sums[0] = sums[1] so next iteration we are comparing the previous value we calculated.

When we’ve iterated through a single time, the “largest” variable will be our answer.

This is O(n) time where n is the size of the given array. This is because we only iterate through it once. The space complexity is O(1) because we only add 2 spaces with the sums array, which is a constant.

32
Q

How do you determine if an array filled with jump values represents a single cycle?

A

Make a for loop that iterates through the array the regular amount of n times. However, keep another int, j, that will represent the indexes being accessed.

Start at 0, store that indexes jump value, set that value in the array to 0, increment j by the jump value, then perform modular arithmetic to keep it in the boundaries of the array. Math.floorMod(j + jump, array.length);

After all the iterations, return false if j does not equal 0. This means it didn’t loop back to the beginning. Also loop through the whole array again and return false if any value is not 0. This means not every index was visited. If both those conditions pass, return true. It is a single cycle.

This is O(n) time with 2 iterations through the array of n size, n being the size of the given array. O(1) space because we’re only using a couple variables. No lists or anything.

33
Q

If given a 2d array of integers with values 0 or 1, how would you return an array containing all the “rivers” in the 2d array? The rivers represent all the array values that are 1. The length of the river is the amount of consistent 1’s connected to each other.

A

Make a 2d boolean array of the same size as the river matrix (This should initialize to all false values, in Java at least).

Traverse through the matrix. If the corresponding value in the boolean matrix is true, this means the number in the river matrix has already been explored, so just skip over it.

If it’s false, set an int “value” to riverDFS(i, j, matrix). If the value returned is greater than 0, add it to the “lengths” array, which holds the lengths of the rivers found.

riverDFS function:

At the start, if this value at (i,j) is true in the booleanMatrix, return 0. First thing after that line is to set that value in the booleanMatrix to true. Next see if that value at (i,j) in the river matrix is 0. If it is, also return zero. Continuing on…

Set variables for top, bottom, left, and right ints to zero. If each of those nodes fits within the bounds (left and top are greater than or equal to 0, right and bottom are less than their width/height values), store the corresponding ints in another recursive all to riverDFS with new values for i or j depending on which you’re searching.

After all the values have been collected, return top+right+bottom+left+1. This will traverse through the whole matrix and return the correct river length values.

This algorithm is O(wh) time where w is the width and h is the height of the matrix. This could also be expressed as O(n) where n is the number of values in the matrix. Will also be O(n) space due to the boolean matrix we create to track visited nodes.

34
Q

If you’re given AncestralTree nodes, topAncestor, descendantOne, and descendantTwo, how would you determine what the youngest parent of the two descendants are?

A

Set placeholder descendants “one” and “two” in the place of the two random descendants. Also set “oneLevel” and “twoLevel” ints to 0 that will track the level of the two descendants.

While the descendants don’t equal the topAncestor, increment one/twoLevel and set one/two to one/two.ancestor. The levels will be recorded in the ints.

Then, while oneLevel and twoLevel are not equal. Shift whichever descendant is at a lower level up and decrement the appropriate level int.

Once the descendants are at the same level, make a 4th while loop that iterates the two descendants up until they equal. Once they equal, the loop will break. Return either descendants at that point and it will be the youngest descendant.

This is O(h) time where h is the height of the tree, or the depth of the lowest descendant. O(1) space because we don’t need more space than storing the variables.

35
Q

If given a matrix of 1’s and 0’s, how would you erase all the 1’s not connected to the edge of the matrix? Erase the islands in other words?

A

Make a booleanMatrix the same size as the given matrix (all values set to false). Also make a public copy of the matrix that will be manipulated.

Iterate through the top row, bottom row, left-most column, and right-most column of the 1’s and 0’s matrix. If a value is 1, send it through the islandDFS function:

This function simply returns if the value is 0 or if its corresponding value in the booleanMatrix is true (signifies it has already been visited and is a one). If it doesn’t return, that means it is a 1 connected to the edge. Set its value to true in the booleanMatrix. Then send all that node’s children through the same function (make sure they exist within the bounds of the matrix).

After doing these 4 for-loops for the edges of the matrix, loop through the whole matrix excluding the outer layer. If any value is 1 and is false within the corresponding booleanMatrix, set it to 0. This will eliminate all 1’s that are not connected to the edge.

This is done in O(n) time where n is the amount of values in the matrix, also O(wh) time. Same space complexity because we make the booleanMatrix the same size.

This can also be done by substituting 1’s connected to the edge with 2’s, then loop through the whole matrix, substituting 2’s for 1’s and 1’s for 0’s. This avoid having to make a booleanMatrix.

However, the space and time complexity are the same. The DFS method could potentially include all positions, and since it’s called recursively, we would technically be creating a stack with all the values on it, so it’s still O(wh) space. Average space is generally better though. Tiny worse time, but insignificant.

36
Q

How would you find if there is a cycle in the graph given an array of adjacency arrays? For example:

edges = [
[1,3],
[2,3,4]
[0],
[],
[2,5],
[]
]

A

Make an int array of 0 values the length of the edges array to keep track of the stack. In this case, it will be length 6. If one of the “edges” arrays is in the stack, its value will change to 1 in the “inStack” array.

Loop through all the entries in “edges”. Call DFS(i, edges) on all cases and set the return value to a boolean called “cycle”. If cycle is true, return true.

DFS function first checks to see if inStack[i] == 1. If it is return true. If not, change inStack[i] to 1 and continue on. Make another for loop with j values this time, iterating through 0 to edges[i].length-1. Do the same thing as the parent function, except DFS will be DFS(edges[i][j], edges). Again, return true if that function returns true.

Set inStack[i] = 0 to remove it from the stack after the for-loop. Have return false at the end of the function to return false if no cycles were found. Also have return false at the end of the parent function to return false if no cycles were found.

Inutitively, we are iterating through all the nodes in the parent function and setting them up as the top ancestor in a tree. In the DFS function, we are building the tree for that particular node and returning if we find a back edge. This back edge means there is a cycle.

37
Q

Given a 2d array of positive and negative integers, how would you return the amount of passes through the matrix needed to turn all negative numbers to positive? Zero numbers are ignored and only negative numbers adjacent to positive numbers can be flipped to positive. Adjacent numbers do not include diagonals.

A

The idea here is to record all the positive integers, flip the adjacent negative numbers to those integers, store those new positive integers in a queue, then flip their adjacent negative numbers. Continue until all numbers are positive.

For this, we need to make a queue that holds the coordinates of the positive integers, so a queue that holds a list of integers two values long that hold i and j (row and column). Also create another queue that will be used as a placeholder between iterations.

First, loop through the whole matrix and add the positive number coordinates to the queue. Then enter the while loop:

while (q.size() > 0) {
while (q.size() > 0) {
assignPositives(q.poll(), matrix)
}
if (qNext.size() > 0) ++passes;
q = qNext;
qNext = new LinkedList<>();
}

This loop assigns negative neighbors their positive numbers on all values in the queue. Throughout that process, new positive numbers are added to “qNext”, which holds the values that will be traversed on the next pass. We can flip the numbers while keeping track of the passes this way.

Don’t add a pass if qNext is empty, because that means that iteration didn’t flip any numbers and isn’t consider a pass. Also don’t add a pass during the initial iteration through the array because we are just extracting the positive numbers. We aren’t changing anything.

After this whole while loop, check the matrix for any negative numbers. If there are, this means we can’t reach them, so return -1.

Else return the number of passes.

This is O(w*h) time for the width and height of the matrix, or O(n) time for all values in the matrix. Space complexity is the same because our queue could potentially hold all values in matrix.

38
Q

Given an array of numbers, k*2 in length, that represent times of tasks, how would you delegate exactly 2 tasks to k workers?
Return an array that holds arrays of the 2 tasks for each worker. Those arrays of 2 in length should hold the indexes of the original array passed to you.

A

Make a List of integer Lists to eventually return. Then make a map that maps task times to indexes in the tasks array. Make another array equal to the tasks array and sort it. Then start a for loop from 0 to k-1.

Each iteration, assign 2 tasks to a worker by getting the next highest time task and the next lowest time task and putting the indexes of those tasks into an array. We do this by getting the task time from sortedTask.get(i) and sortedTasks.get(sortedTasks.size()-1-i). Then use those times to get a matching index value from the map. Add those indexes to a new list and add that array to the array we’ll be returning. After adding those two jobs, remove those indexes from the map so they won’t be used again.

public List<List<Integer>> taskAssignment(int k, ArrayList<Integer> tasks) {
// Write your code here.
List<List<Integer>> taskAssignments = new ArrayList<List<Integer>>();
List<Integer> sortedTasks = new ArrayList<>();
Map<Integer, List<Integer>> map = new HashMap<>();</Integer></Integer></Integer></Integer></Integer></Integer>

tasks.forEach(n -> System.out.print(n+" "));
System.out.println();

for (int i = 0; i < tasks.size(); ++i) {
  if (map.get(tasks.get(i)) == null) {
    List<Integer> newList = new ArrayList<>();
    newList.add(i);
    map.put(tasks.get(i), newList);
  } else {
    List<Integer> newList = map.get(tasks.get(i));
    newList.add(i);
    map.put(tasks.get(i), newList);
  }
}

map.forEach((key,v) -> {
  System.out.print(key + ": ");
  System.out.println(v);
});
// O(nlogn)
tasks.stream().sorted().forEach(n -> sortedTasks.add(n));

for (int i = 0; i < k; ++i) {
  List<Integer> newList = new ArrayList<>();
  int lowIndex = map.get(sortedTasks.get(i)).get(0);
  int highIndex = map.get(sortedTasks.get(sortedTasks.size()-1-i)).get(0);
  newList.add(lowIndex);
  newList.add(highIndex);
  taskAssignments.add(newList);

  List<Integer> mapReplaceList = map.get(sortedTasks.get(i));
  mapReplaceList.remove(0);
  map.put(sortedTasks.get(i), mapReplaceList);
  
  mapReplaceList = map.get(sortedTasks.get(sortedTasks.size()-1-i));
  mapReplaceList.remove(0);
  map.put(sortedTasks.get(sortedTasks.size()-1-i), mapReplaceList);      
}

return taskAssignments;   }
39
Q

Given a list of fuel at cities, a list of road lengths that connect those cities in a circle, and miles per gallon, how do you find the city you would need to start at to go every city? The number of gallons provided is just enough to make this trip, so only one city is the answer.

A

Make three variables, one for currentFuel, smallestFuel, and smallestIndex. Initialize them all to 0. Iterate through all the cities, 0 through fuel.length-1. Inside the loop:

currentFuel += (fuel[i] * mpg) - distances[i];
if (currentFuel < smallestFuel) {
smallestFuel = currentFuel;
smallestIndex = i+1;
}

return smallestIndex;

The city where the current fuel is the lowest is the city you need to start at. This is O(n) time and O(1) space.

40
Q

How would you reverse a string with multiple words in it while keeping the words facing forward? You must include whitespace as well so:

“Bill Jerry Tom”
“Tom Jerry Bill”

A

Create a new empty string to hold the new value to return.

Iterate through the given string starting at the end and decrementing the pointer. Keep track of the endIndex, which starts at originalString.length(). When you find a whitespace value, set the startIndex to i+1. Now the startIndex and endIndex wrap the last word in the string, so grab that substring and add it to the new string. While there are whitespaces, add whitespace to the new string and continue to decrement the pointer each time. Once you’re not on a whitespace anymore, set the endIndex to i+1, and the loop will restart.

When the loop is done, you’ll need to add the starting word to the end:

newString += string.substring(0, endIndex);

This process will add all the strings to the new string but in reverse order and also add the appropriate whitespace.

41
Q

How would you remove a value in a singly linked list kth positions from the end given the head of the list and k?

A

Set a “length” variable to 0 and iterate through the linked list until node.next == null. You now have the length of the list.

Set the current node back to the head and iterate while i < length-k. You’re now on the node right before the node you’re trying to remove.

If k <= length, this means you’re not removing the head node, so just do node.next = node.next.next; This skips over the one you’re trying to remove, which does effectively remove it. If it is the head you’re trying to remove:

head.value = node.next.value;
head.next = node.next.next;

This runs in O(n) time as you’d iterate through the list at most 2 full times. O(1) space for a couple declared variables.

42
Q

How do you create a list of all different permutations given an array of numbers?

A

Create a new list “perm” that holds a permutation and another list “perms” that holds a list of permutations. Pass the given array, perm, and perms through a helper function:

public static void helper(List<Integer> array, List<Integer> perm, List<List<Integer>> perms) {
if (array.size() == 0) {
perms.add(perm);
System.out.println(perms);
} else {
for (int i = 0; i < array.size(); ++i) {
List<Integer> newArray = new ArrayList<>(array);
List<Integer> newPerm = new ArrayList<>(perm);
newPerm.add(array.get(i));
newArray.remove(i);
helper(newArray, newPerm, perms);
}
}
}</Integer></Integer></Integer></Integer></Integer>

This function checks to see if the array is empty, which intuitively means that this iteration has placed all values in the original array into the “perm” array in a unique order and can now be added to the “perms” array.

The for loop recursively calls the helper function to make all different possible orderings of the given array. The first time the for loop is called, the array is full, so it iterates through all values. At the level below that, however, the first value, which has been placed in the “perm” array has been removed from the original, so at that level, it iterates through all values except the first. The second iteration of the original for-loop does the same thing, except this time the second value is the first one placed into the permutation and the other values are ordered differently. This process branches off into many other helper functions to discover every possibility.

43
Q

How would you get the powerset of an array of numbers? The powerset is the set of all subsets. The sets don’t need to be in a particular order.

A

Make a List<List<Integer>> arrayList of the powerset and put the empty set inside it. Make 2 nested for-loops, the outer one iterating through the given array of numbers and the inner one iterating through the current powerSet.</Integer>

Note: you need to set the current powerSet size in the outer loop before you begin the inner loop, and have the inner loop stop at that size you record since the inner loop will be increasing the size of the powerset. If you don’t do this, it will go forever.

The inner loop: for all subsets currently in the subset, make an identical subset, add the number you’re at in the outer for loop, and add that new subset to the powerset.

public static List<List<Integer>> powerset(List<Integer> array) {
// Write your code here.
List<List<Integer>> powerSet = new ArrayList<>();
List<Integer> subset = new ArrayList<>();
powerSet.add(subset);</Integer></Integer></Integer></Integer>

for (int i = 0; i < array.size(); ++i) {
  int size = powerSet.size();
  for (int j = 0; j < size; ++j) {
    subset = new ArrayList<>(powerSet.get(j));
    subset.add(array.get(i));
    System.out.println(subset);
    powerSet.add(subset);
  }
}

return powerSet;   }

O((2^n)n) time and space. You double the amount of subsets each iteration, so that’s 2^n iterations. To make those subsets, you have to add numbers to those subsets, which takes time. On average, it takes n/2 time to make each subset, which then converges to O((2^n)n) time. The powerSet takes an equal amount of space.

44
Q

Given a number sequence that can be mapped to letters on a phone (2 is “abc” etc.) make all the possible letter sequences of that given number sequence.

A

Make a List of Strings that you’ll return and also an empty string that will hold the current string you’re building. Pass the given phone number, current string, and list of strings to a helper function:

if the phone number length is 0, add current to the list. Else, pass them all to another helper function called getCombos. This function will branch off the current string into all other possible strings given the next number in the phone number.

Record the character representation of the next number in the sequence. Due to the way the helper functions are set up, this will always be number.get(0); Make a StringBuilder that is equal to the current number. The StringBuilder class will allow you to get rid of the first number in the sequence, which is what you’ll do, and pass the result to the first helper function.

Make if/else-if statements for each possible number 0-9. Here are a couple so you get the idea:

if (c == ‘1’) {
helper(newNumber.toString(), current + ‘1’, fullList);
} else if (c == ‘2’) {
helper(newNumber.toString(), current + ‘a’, fullList);
helper(newNumber.toString(), current + ‘b’, fullList);
helper(newNumber.toString(), current + ‘c’, fullList);
} else if (c == ‘3’) {
helper(newNumber.toString(), current + ‘d’, fullList);
helper(newNumber.toString(), current + ‘e’, fullList);
helper(newNumber.toString(), current + ‘f’, fullList);
}

This will branch appropriately all the possible values for the given numbers and append that value to the “current” string within that branch. Because we’ve deleted the first character in the number sequence too, the first helper function will know when all numbers have been used and will add that possible result to the list.

This is O((n^4)n) time. This is because, due to the numbers that have 4 different possibilities, you could quadruple the current amount of possibilities each iteration. It also takes n time to create each of those strings because each is as long as the original phone number (I think this is why?). The space is the same because there will be the same, O((n^4)n) which is the greatest possible length of the resulting list * the number of characters in each string.

45
Q

If given a staircase of a certain height and a max amount of steps you can take at a time, how can you find all the ways to traverse the staircase? For example, with:

height = 4
maxSteps = 2

The answer is 5
1, 1, 1, 1
1, 1, 2
1, 2, 1
2, 1, 1
2, 2

How would you do this faster just finding the number of ways without actually making the array of ways?

A

Make a list of integer lists called “ways” and an interger list called “currentWay”.

Make a helper function that takes the height given, maxSteps given, currentWay, ways, currentHeight (starts at 0). The helper function:

If currentHeight == height, add currentWay to ways. Else if currentHeight < height, start a for-loop starting at 1 and ending at maxSteps. For each step length, make a new array identical to the currentWay array. Then add the current value of i to it. Call the helper function again and call all the same stuff, except make “newWay” is the new currentWay and currentHeight is currentHeight+i;

The resulting “ways” array will be the answer. Because the helper function only does stuff when currentHeight is less than or equal to the staircase height, all heights that go over will be filtered and ignored.

To get the number, you could return the length of that array, but the time complexity will be pretty bad.

46
Q

If given a sorted matrix, where every value to the right or below the current value is greater, how would you find a target value in linear time, or O(n+m) time?

A

Start at the top right of the array where i = 0 and j = matrix[0].length-1. Also make an int to hold the current value of the matrix. While the current value does not equal the target value:

If we have reached the bottom left of the 2d array, the value is not in the matrix, so return [-1, -1].

If the current value is greater than the target, the whole column below can be ignored because they’re all greater, so decrement j to go left and reset the current value to matrix[i][j].

If the current value is lower than the target, the whole row to the left can be ignored because they’re all smaller, so increment i to go down and reset the current value to matrix[i][j].

Before decrementing j or incrementing i, check to see if they are out of bounds of the matrix. Return [-1, -1] if this is the case because this also means the target value is not in the matrix.

The while loop will break if the matrix at (i, j) ever reaches the target, so send back those coordinates in an int array.

This is O(n+m) time where n and m are the width and length of the given matrix. O(1) space.

47
Q

How would you sort an array filled with 3 different scattered numbers and another array indicating the order which those numbers should be ordered in? You can’t use any other data structure besides pointers to achieve this.

A

Set an i pointer at the beginning of the array and a j pointer at the end. Then make a while loop while i < j.

Increment the i pointer until it points to a value that belongs at the end of the array (order[2]). Decrement j while it points to the same value since that’s exactly what we want at the end of the array. Also include in those inner while statements conditions so i and j don’t go out of bounds on either side. If i is still less than j are those loops, swap the values at that place.

Since we know i is pointing to a value equal to order[2], we don’t even need to make a placeholder. Just array[i] = array[j]; array[j] = order[2];

After the outer while loop, all the last values should be in place. You can keep j where it is, but set i back to 0. Now we’ll do the same thing for the values that should be in the middle. The code will be identical, except wherever it said order[2], make it say order[1].

After that second while loop, return the sorted array.

This takes O(n) time for iterating through the array no more than twice and O(1) space.

48
Q

How do you ensure brackets are balanced within a string? This includes all these “()[]{}” brackets. Random sequences between the brackets in the string should not affect it.

A

Make a stack of Characters and loop through the string once.

If the current character is one of these “(, [, {“ add it to the stack and continue. If the character is one of these “), ], }” do this:

Check if the stack is empty. If it is, there is no matching beginning bracket and the string is not balanced, so return false.

Next, pop a Character off the stack and store it. Write 3 if statements for the 3 different cases to make sure the ending bracket matches the appropriate starting bracket popped off the stack. If they don’t match, return false.

After the for loop, return false if the stack is not empty. This means there are 1 or more starting brackets without a matching ending bracket.

Return true if it passes all those tests.

This is O(n) time as it passes through the string once. It is also O(n) space because the stack may hold the whole string in it in the worst case.

49
Q

Given an array that represents building heights, how do you return an array of indexes that show which buildings can see the sunset? A direction is given to represent which way the sunset is facing. Only buildings strictly taller than all others between them and the sun can see the sunset.

A

Initialize the ArrayList “sees” to keep track of indexes. Also create a “tallest” variable to keep track of the tallest building so far.

If direction is WEST, start at the i = 0, the west-most building, in a for-loop. (Remember when checking if the direction is west, use .equals(). Not == for a string.) If the current building has a greater height than “tallest”, make its height the tallest and add the current index i to the “sees” array. This will fill the array appropriately.

If the direction is east, start at the right end and do the exact same thing. The only difference is that the indexes must be added to the beginning of the array instead of the end going in this direction. Use sees.add(0, i) to add to the front of the list.

O(n) time and O(n) space where n is the size of the buildings height array. Loops through the array once and we make an array that could be n size in the worst case.

50
Q

How would you sort a stack without using any other data structure?

A

Return a “sort” helper function that takes the current stack as a parameter:

If the stack is empty, return it because an empty stack is technically sorted. Else, continue on. Pop the top element off the stack and call sort again on the new stack. Then after that returns, call another helper function called insert that takes the stack and the value of “top” at that recursive call and inserts it into the stack. After that returns, return the stack.

The insert function should add the number passed to it if the stack is empty or if the value is greater than the value on top of the stack. Otherwise, pop the top element, call insert again with the original value passed to it, and when that returns, add the top value you just popped off back onto the stack.

Intuitively, at each layer of the sort function, we’re taking an element off the stack and sorting the sub-stack of all elements beneath it before inserting that value back into it. That’s why it is organized by popping the top off, sorting the resulting stack, then calling insert on the value to put it back into the stack.

The insert function can only properly insert something into the stack if all other values in the stack are sorted. So the sort function empties the stack, which means it’s sorted, then starts using insert recursively to add all the elements back onto it in a sorted manner at each layer. Then when insert is called again on a layer above it, it just needs to place the element in question appropriately and the rest can be popped and pushed back on in their original order.

This is O(n^2) as each element must be removed from stack at each recursive call, and each of those layers can possibly require us to remove all other elements on the stack and put them back on due to the insert function. So n layers and n possible operations at each layer. It is O(n) space because we create a recursive function call stack of size n to pop all the elements off the stack.

51
Q

Given an array of integers, how would you make an array of the same size where each number represents the next greatest integer relative to the index of the original array? Values with not value higher should be -1 in the output array. For example:

Input: [2, 5, -3, -4, 6, 7, 2]
Output: [5, 6, 6, 6, 7, -1, 5]

A

Make the output array of the same size as the input array and initialize the values to -1. Also make a stack. This will hold indexes of the original array that have not been assigned a next greatest number.

Loop twice through the array by making a normal for loop that would go through once, except make it i < array.length*2. Then directly inside it, int j = i % array.length. Use j to iterate through the array as i will go out of bounds.

While the stack is not empty and array[j] > array[stack.peek()], output[stack.pop()] = array[j]. After that while loop, push j onto the stack so that that index can later be assigned a next greatest integer.

After two loops through the array, the elements still on the stack are the indexes that don’t have a next greatest integer. This process works retroactively, taking the current number and seeing if the indexes before it can use it as their next greatest integer and stopping at any value greater than it.

This is done in O(n) time because we iterate through the array twice and there we can’t do any more than O(2n) pushes and pops total, so this is linear. The space is also O(n) for the stack and output array, which can’t be greater than O(n).

52
Q

How do you find the largest substring that is a palindrome in a given string?

A

If the string length is <= 1, this is a palindrome itself, so just return it.

Otherwise, make a “longest” string variable to keep track of the longest string. Make a for loop that will loop through all the characters one time, but start at index 1 instead of 0.

There are two things we’re checking for at each index. Is the character we’re on the center of the palindrome or is the character we’re on plus the one behind it the center of the palindrome?

For the character itself being the center, add it to a string called “current”. Also make ‘j’ and ‘k’ index variables that will branch out from that center. Initialize j to i-1 and k to i+1.

While current is a palindrome (make a helper function to check if a string is a palindrome for this), do the following:

If the length of the current palindrome is greater than “longest” length, longest equals current.

If j and k are within the boundaries of the original string, add the appropriate characters to the front and back of the current string using a StringBuilder. Decrement j again and increment k again.

This process will keep adding a shell to the current string and update the longest string as long as it remains a palindrome. If i and/or k is out of bounds, break the while loop.

Now for the second case where the character at str.charAt(i) and the character to its left are the center. Set:

current = “” + str.charAt(i-1) + str.charAt(i);
j = i-2;
k = i+1;

Repeat the exact same while loop as described above to do the same thing.

At the end of the for-loop “longest” will have the longest palindrome.

This is o(n^2) time in the longest case because we loop through the array, which is n. Then at each iteration we are doing potentially n amount of work adding the whole rest of the string to the current character. O(n) space to potentially hold the entire string in the “longest” string.

53
Q

How would you group anagrams together in separate lists given an array of Strings?

A

Create List<List<String>> anagrams to hold the anagrams. Also create a Map<String, List<String>> to map alphabetized strings to a list of strings that alphabetize to the same thing. Also make a helper function that alphabetizes a given string:</String></String>

public static String getAlphabetical(String s) {
char[] array = s.toCharArray();
Arrays.sort(array);
return new String(array);
}

Iterate through the given list of words. For each word, get the alphabetized word (key) and retrieve the currentList (value) from the map. If the currentList is null, initialize it to a new ArrayList<>();

Add words.get(i) to the list and update the map by putting the updated list into the map.

After the for loop, loop through the map with forEach and add all the lists to “anagrams”. Return anagrams. Each list within will hold words made of the exact same letters.

This will be O(w * nlogn) time, where w is the lengths of the “words” array and n is the length of the longest word. This is because sorting the words to their alphabetized form takes O(nlogn) time. This times the amount of words to iterate through is O(w) time. It is O(wn) space because the length of the map will hold w words that are potentially all n in length in the worst case.

54
Q

How would you find out if an array of integers has a zero sum subarray?

A

Make a set. Make an int initialized to zero called ‘current’. Traverse through all the numbers in the given array and add that value to ‘current’ to determine the subarray from zero. Each time you add to current, add it to the set you made at the start.

If it ever returns false (the number you’re adding is already in the set), this means there is a zero sum subarray in the given numbers.

Intuitively, this means there was a subarray before that, if you added to the current subarray, would equal itself. That means the subarray it adds to must equal zero, so a zero sum subarray exists.

public boolean zeroSumSubarray(int[] nums) {
// Write your code here.
Set<Integer> sums = new HashSet<>();
int current = 0;
for (int i = 0; i < nums.length; ++i) {
current += nums[i];
if (!sums.add(current) || current == 0) return true;
}
return false;
}</Integer>

55
Q

How would you merge two linked lists that are already sorted in place (without using an alternate data structure)?

A

Make new linked lists, p1 and p2, that act as pointers to the two given linked lists. Make another pointer p1Prev and set it to null.

Make a while loop that continues while p1 and p2 are not null.

If p1.value is less than p2.value, this means p1 is already in the correct spot and needs no switching with nodes in p2. Set p1Prev to p1 and p1 to p1.next.

If p1.value is greater than p2.value, check if p1Prev is not null. If it is, set p1Prev.next to p2. Then p1Prev = p2, p2 = p2.next, and p1Prev.next = p1.

After the while loop, if p1 is null, set p1Prev.next to p2 to append the rest of p2 to the end of the linked list. It should all be sorted correctly.

Return whichever linked list’s starting value was lower.

Intuitively, p1Prev starts as null, and each iteration it gets set to the next number in the sequence. By setting p1Prev.next to either p1 or p2, its redirecting both linked lists to intertwine with each other. p1 and p2 iterate as if they are traversing their respective lists normally, and p1Prev goes behind them and intertwines them.

public static LinkedList mergeLinkedLists(LinkedList headOne, LinkedList headTwo) {
// Write your code here.
// O(n+m) time | O(n) space
LinkedList p1 = headOne;
LinkedList p2 = headTwo;
LinkedList p1Prev = null;

while (p1 != null && p2 != null) {
  if (p1.value < p2.value) {
    p1Prev = p1;
    p1 = p1.next;
  } else {
    if (p1Prev != null) {
      p1Prev.next = p2;
    }
    p1Prev = p2;
    p2 = p2.next;
    p1Prev.next = p1;
  }
}

if (p1 == null) {
  p1Prev.next = p2;
}
if (headOne.value < headTwo.value) return headOne;
else { return headTwo; }   }
56
Q

How would you find all valid IP addresses in a given string of numbers? A valid IP address has 4 sections separated by periods that are within the range 0-255 and don’t have leading zeros.

A

Make 3 nested for loops that grab all possible sections based on where the 3 periods would be placed to create the 4 sections of the IP address. Make a helper function to see if a section is valid. Check all 4 subsections to see if they’re valid, and if they are, add that variation with the appropriate periods.

public ArrayList<String> validIPAddresses(String string) {
// Write your code here.
ArrayList<String> ipList = new ArrayList<>();</String></String>

for (int i = 0; i < string.length()-3; ++i) {
  for (int j = i+1; j < string.length()-2; ++j) {
    for (int k = j+1; k < string.length()-1; ++k) {
      String p1 = string.substring(0, i+1);
      String p2 = string.substring(i+1, j+1);
      String p3 = string.substring(j+1, k+1);
      String p4 = string.substring(k+1);

      if (isValidSection(p1) && isValidSection(p2) && 
          isValidSection(p3) && isValidSection(p4)) {
        ipList.add(new String(p1 + "." + p2 + "." + p3 + "." + p4));
      }
    }
  }
}
return ipList;   }

public boolean isValidSection(String section) {
return Integer.parseInt(section) < 256 && (section.length() > 1 && section.charAt(0) != ‘0’ || section.length() == 1);
}

57
Q

How would you find the minimum number of letters needed to create all words in a string array?

A

Make a map to hold all the characters. It should map the character to an integer that represents how many of that character we need.

Make a for loop that loops through all the words. Make a map for each individual word with the number of characters it needs to create that words.

After that map is made, compare it to the original map. If some character value is greater than the value in the original map, update the original map with the greater value.

Once the map is populated with the appropriate values, make an empty string and iterate through the map. Each iteration, make a for loop that adds the current character to the string n times, where n is the value linked to the character in the map.

Return the string we’ve populated converted to a char array.