Image for post

Image courtesy of the author

Landing a job at Amazon is a dream for many developers around the globe. Amazon is one of the largest companies in the world, with a workforce over half a million strong. Amazon is hiring at a rapid rate with a unique hiring process that emphasizes company culture and leadership principles. Today, I will walk through everything you need to crack an Amazon interview, including coding questions and a step-by-step preparation guide.

Today we will go over the following:

  • 45 common Amazon coding interview questions
  • Overview of Amazon coding interviews
  • How to prepare for a coding interview
  • Wrapping up

Image for post

Image courtesy of the author


45 Common Amazon Coding Interview Questions

1. Find the missing number in the array

You are given an array of positive numbers from 1 to n, such that all numbers from 1 to n are present except one number x. You have to find x. The input array is not sorted. Look at the below array and view the solution in Python. Click here to view the solution in C++, Java, JavaScript, and Ruby.

Image for post

def find_missing(input):

# calculate sum of all elements
# in input list
sum_of_elements = sum(input)
# There is exactly 1 number missing
n = len(input) + 1
actual_sum = (n * ( n + 1 ) ) / 2
return actual_sum - sum_of_elements
def test(n):
 missing_element = random.randint(1, n)
 v = []
 for i in range(1, n):
 if i != missing_element:
    v.append(i)
actual_missing = find_missing(v)
print("Expected Missing = ", missing_element, " Actual Missing = ", actual_missing)
assert missing_element == actual_missing
def main():
   for n in range(1, 10):
   test(1000000)
main()

Runtime Complexity: Linear, O(n)

Memory Complexity: Constant, O(1)

A naive solution is to simply search for every integer between 1 and n in the input array, stopping the search as soon as there is a missing number. But we can do better. Here is a linear, O(n) solution that uses the arithmetic series sum formula.​​ Here are the steps to find the missing number:

  • Find the sum sum_of_elements of all the numbers in the array. This would require a linear scan, O(n).
  • Then find the sum expected_sum of first n numbers using the arithmetic series sum formula
  • The difference between these, i.e., expected_sum - sum_of_elements, is the missing number in the array.

2. Determine if the sum of two integers is equal to the given value

Given an array of integers and a value, determine if there are any two integers in the array whose sum is equal to the given value. Return true if the sum exists and return false if it does not. Consider this array and the target sums. Click here to view the solution in C++, Java, JavaScript, and Ruby.

Image for post

def find_sum_of_two(A, val):
  found_values = set()
  for a in A:
     if val - a in found_values:
        return True
     found_values.add(a)
return False
v = [5, 7, 1, 2, 8, 4, 3]
test = [3, 20, 1, 2, 7]
for i in range(len(test)):
  output = find_sum_of_two(v, test[i])
  print("find_sum_of_two(v, " + str(test[i]) + ") = " + str(output))

Runtime Complexity: Linear, O(n)

Memory Complexity: Linear, O(n)

You can use the following algorithm to find a pair that add up to the target (say, val).

  • Scan the whole array once and store visited elements in a hash set.
  • During scan, for every element e in the array, we check if val - e is present in the hash set, i.e., val - e is already visited.
  • If val - e is found in the hash set, it means there is a pair (eval - e) in the array whose sum is equal to the given val.
  • If we have exhausted all elements in the array and didn’t find any such pair, the function will return false.

3. Merge two sorted linked lists

Given two sorted linked lists, merge them so that the resulting linked list is also sorted. Consider two sorted linked lists and the merged list below them as an example. Click here to view the solution in C++, Java, JavaScript, and Ruby.

Image for post

def merge_sorted(head1, head2):
  # if both lists are empty then merged list is also empty
  # if one of the lists is empty then other is the merged list
if head1 == None: 
   return head2
elif head2 == None:
   return head1
mergedHead = None;
if head1.data <= head2.data:
   mergedHead = head1
   head1 = head1.next
else:
   mergedHead = head2
   head2 = head2.next
mergedTail = mergedHead
while head1 != None and head2 != None:
   temp = None
   if head1.data <= head2.data:
   temp = head1
   head1 = head1.next
else:
   temp = head2
   head2 = head2.next
 mergedTail.next = temp
 mergedTail = temp
if head1 != None:
   mergedTail.next = head1
elif head2 != None:
   mergedTail.next = head2
return mergedHead
array1 = [2, 3, 5, 6]
array2 = [1, 4, 10]
list_head1 = create_linked_list(array1)
print("Original1:")
display (list_head1)
list_head2 = create_linked_list(array2)
print("\nOriginal2:")
display (list_head2)
new_head = merge_sorted(list_head1, list_head2)
print("\nMerged:")
display(new_head)

Runtime Complexity: Linear, O(m+n) where m and n are lengths of both linked lists

Memory Complexity: Constant, O(1)

Maintain a head and a tail pointer on the merged linked list. Then choose the head of the merged linked list by comparing the first node of both linked lists. For all subsequent nodes in both lists, you choose the smaller current node, link it to the tail of the merged list, and move the current pointer of that list one step forward.

Continue this while there are some remaining elements in both the lists. If there are still some elements in only one of the lists, you link this remaining list to the tail of the merged list. Initially, the merged linked list is NULL.

Compare the value of the first two nodes and make the node with the smaller value the head node of the merged linked list. In this example, it is 4 from head1. Since it’s the first and only node in the merged list, it will also be the tail. Then move head1 one step forward.

4. Copy linked list with arbitrary pointer

You are given a linked list where the node has two pointers. The first is the regular next pointer. The second pointer is called arbitrary_pointer, and it can point to any node in the linked list. Your job is to write code to make a deep copy of the given linked list. Here, deep copy means that any operations on the original list should not affect the copied list. Click here to view the solution in C++, Java, JavaScript, and Ruby.

def deep_copy_arbitrary_pointer(head):
  if head == None:
    return None

current = head;
new_head = None
new_prev = None
ht = dict()
# create copy of the linked list, recording the corresponding
# nodes in hashmap without updating arbitrary pointer
while current != None:
   new_node = LinkedListNode(current.data)
# copy the old arbitrary pointer in the new node
   new_node.arbitrary = current.arbitrary;
if new_prev != None:
   new_prev.next = new_node
else:
   new_head = new_node
ht[current] = new_node
   new_prev = new_node
   current = current.next
new_current = new_head
# updating arbitrary pointer
while new_current != None:
  if new_current.arbitrary != None:
    node = ht[new_current.arbitrary]
    new_current.arbitrary = node
  new_current = new_current.next
return new_head
def create_linked_list_with_arb_pointers(length):
  head = create_random_list(length)
  v = []
  temp = head
  while temp != None:
    v.append(temp)
    temp = temp.next
  for i in range(0, len(v)):
    j = random.randint(0, len(v) - 1)
    p = random.randint(0, 100)
    if p < 75:
      v[i].arbitrary = v[j]
return head

Runtime Complexity: Linear, O(n)

Memory Complexity: Linear, O(n)

This approach uses a map to track arbitrary nodes pointed by the original list. You will create a deep copy of the original linked list (say list_orig) in two passes.

  • In the first pass, create a copy of the original linked list. While creating this copy, use the same values for data and arbitrary_pointer in the new list. Also, keep updating the map with entries where the key is the address to the old node and the value is the address of the new node.
  • Once the copy has been created, do another pass on the copied linked list and update arbitrary pointers to the new address using the map created in the first pass.

5. Level order traversal of binary tree

Given the root of a binary tree, display the node values at each level. Node values for all levels should be displayed on separate lines. Let’s take a look at the below binary tree. Click here to view the solution in C++, Java, JavaScript, and Ruby.

Image for post

# Using two queues

def level_order_traversal_1(root):
  if root == None:
    return
  queues = [deque(), deque()]
  current_queue = queues[0]
  next_queue = queues[1]
  current_queue.append(root)
  level_number = 0
while current_queue:
   temp = current_queue.popleft()
   print(str(temp.data) , end = " ")
   if temp.left != None:
     next_queue.append(temp.left)
  if temp.right != None:
     next_queue.append(temp.right)
  if not current_queue:
     print()
     level_number += 1
     current_queue = queues[level_number % 2]
     next_queue = queues[(level_number + 1) % 2]
 print()
arr = [100,50,200,25,75,350]
root = create_BST(arr)
print("InOrder Traversal:", end = "")
display_inorder(root)
print("\nLevel Order Traversal:\n", end = "")
level_order_traversal_1(root)

Runtime Complexity: Linear, O(n)

Memory Complexity: Linear, O(n)

Here you are using two queues: current_queue and next_queue. You push the nodes in both queues alternately based on the current level number.

You’ll dequeue nodes from the current_queue, print the node’s data, and enqueue the node’s children to the next_queue. Once the current_queue becomes empty, you have processed all nodes for the current level_number. To indicate the new level, print a line break (\n), swap the two queues, and continue with the above-mentioned logic.

After printing the leaf nodes from the current_queue, swap current_queue and next_queue. Since the current_queue would be empty, you can terminate the loop.

#programming #interview #amazon #job-interview #coding-interviews

Cracking the Amazon Interview
8.35 GEEK