Home/Blog/Interview Prep/Google coding interview questions: Top 18 questions explained
Home/Blog/Interview Prep/Google coding interview questions: Top 18 questions explained

Google coding interview questions: Top 18 questions explained

Aaron Xie
Jun 26, 2024
23 min read
content
Google Coding Interview SeriesThe definitive prep guide to the interview processTop Google coding questions explainedCrack coding interviews by building these 5 real-world featuresAnswer any interview problem by learning the patterns behind common questions.Google coding interview recap18 common Google coding interview questions1. Find the kth largest element in a number streamExamplesTry it yourselfSolution summary2. Find k closest numbersExamplesTry it yourselfSolution summary3. Sum of two valuesExamplesTry it yourselfSolution summary4. Delete node with a given keyExamplesTry it yourselfSolution summary5. Copy linked list with arbitrary pointerExampleTry it yourselfSolution summary6. Mirror binary tree nodesExampleTry it yourselfSolution summary7. Check if two binary trees are identicalExamplesTry it yourselfSolution summary8. String segmentationExampleTry it yourselfSolution summary9. Find all palindrome substringsExampleTry it yourselfSolution summaryAnswer any interview problem by learning the patterns behind common questions.10. Largest sum subarrayExampleTry it yourselfSolution summary11. Determine if the number is validExamplesTry it yourselfSolution summary12. Print balanced brace combinationsExampleTry it yourselfSolution summary13. LRUExampleTry it yourselfSolution summary14. Find Low/High IndexExampleTry it yourselfSolution summary15. Merge overlapping intervalsExamplesTry it yourselfSolution summary16. Path sumExampleTry it yourselfSolution summary17. Find the missing numberExamplesTry it yourselfSolution summary18. Reverse a linked listExampleTry it yourselfSolution summaryMore practice and preparationWrapping up and resourcesContinue reading about coding interview prep
share

Google Coding Interview Series#

  • The definitive prep guide to the interview process#
  • Top Google coding questions explained#
  • Crack coding interviews by building these 5 real-world features#

Landing a tech job at Google is no easy feat. Like other large tech giants, Google’s software engineering interview process is comprehensive and difficult, requiring weeks of preparation and practice. So, how can you prepare for the coding interview? What interview questions should prepare for?

Today we’ll revisit our Google Coding interview series and breakdown 18 common interview questions asked by Google recruiters.

Today we’ll cover the following:

Google coding interview recap#

The entire Google interview process takes around two months to complete and consists of five interviews in total. For the interviews, there will primarily be three question formats: system design interview, general analysis, and technical skills.

Prescreen: The first interview consists of a prescreening with a Google employee, which will last 45 - 60 minutes. In this interview, the interviewer will test you on your knowledge of data structures and algorithms.

On-site interviews: If you past the prescreen, you will be invited to an on-site interview, in which you will be interviewed by 4-6 employees for 45 minutes each. These interviews will more rigorously test your knowledge of data structures and algorithms. Typically, you will be writing code on a Google Doc or whiteboard.

Decision: Based on your performance, you will be scored on a scale between 1 and 4, with 3 being the minimum threshold to hire.

Overall, Google wants to test your technical and logical application of computer science. Here are some popular topics to review before your interview:


18 common Google coding interview questions#

1. Find the kth largest element in a number stream#

Design a class to efficiently find the kthk^{th} largest element in a stream of numbers. The class should have the following two things:​

  • The constructor of the class should accept an integer array (nums) containing initial numbers from the stream and an integer kk.

  • The class should expose a function add(int value) which will add the number value to the stream of numbers, and will return the kthk^{th} largest number in the stream seen so far.

Examples#

Input: [4, 1, 3, 12, 7, 14], k = 3
Calling add(6) should return '7'.
Calling add(13) should return '12'.
Calling add(4) should still return '12'.

Try it yourself#

Implement your solution in the following coding playground:

import heapq
class KthLargest:
# Constructor to initialize heap and add values in it
def __init__(self, k, nums):
pass
# Adds element in the heap and return the Kth largest
def add(self, val):
# Write your code here
return -1

Time complexity of __init__: O((nk)logn)=O(nlogn)O((n-k) \log n) = O(n \log n)

Time complexity of add: O(logk)O(\log k), where kk represents the size of the heap.

Space complexity of __init__: O(k)O(k)

Space complexity of add: O(k)O(k), where kk represents the size of the heap.

Solution summary#

The KthLargest class is designed to efficiently maintain the kk largest elements within a given list using a min heap. Here’s a breakdown of the solution:

  • Initialization: Upon initialization, the constructor sets the instance variables k and nums, where k denotes the kthk^{th} largest element to be tracked, and nums represents the input list of numbers. The constructor heapifies the list and prunes elements from the heap until its length is reduced to k, ensuring only the kk largest elements remain.

  • Adding elements: The add method is responsible for adding a new element val into the heap. If the length of nums exceeds k after adding the new element, the method heappop is used to remove the smallest element from the heap. Subsequently, it returns the smallest element in the heap, which corresponds to the kthk^{th} largest element.

2. Find k closest numbers#

You are given a sorted array of integers, nums, and two integers, target and k. Your task is to return k number of integers that are closest to the target value, target. The integers in the output array should be in sorted order.

  • An integer, nums[i], is considered to be closer to target, as compared to nums[j] when |nums[i] - target| < |nums[j] - target|. However, when |nums[i] - target| == |nums[j] - target|, the smaller of the two values nums[i] and nums[j] is selected.

Examples#

Input: nums = [1, 2, 3, 4, 5], k = 4, target = 3
Output: [1, 2, 3, 4]
Input: nums = [2, 4, 5, 6, 9], k = 3, target = 6
Output: [4, 5, 6]

Try it yourself#

Implement your solution in the following coding playground:

main.py
binary_search.py
def binary_search(array, target):
left = 0
right = len(array) - 1
while left <= right:
mid = (left + right) // 2
if array[mid] == target:
return mid
if array[mid] < target:
left = mid + 1
else:
right = mid - 1
return left

Time complexity: O(logn+k)O(\log n + k)

Space complexity: O(1)O(1)

Solution summary#

The find_closest_elements function efficiently identifies the kk closest elements to a given target value within a sorted list of numbers. Here’s an overview of the solution:

  • Edge cases handling: If the length of nums is equal to the value in the variable k, the function returns the entire list since all elements are closest to the target. Similarly, if the target is less than or equal to the first element of nums, it returns the first kk elements. Likewise, if the target is greater than or equal to the last element of nums, it returns the last kk elements.

  • Binary search for closest element: The function utilizes the binary_search function to find the index of the closest element to the target in the sorted list nums.

  • Window expansion: After finding the index of the closest element, the function expands a window around this element to capture the kk closest elements. It iteratively adjusts the window boundaries based on the proximity of elements to the target until the window contains exactly kk elements.

  • Result extraction: Finally, the function returns the elements within the determined window, representing the kk closest numbers to the target.

3. Sum of two values#

Given an array of integers arr and a target value t, find two array indices such that the values stored at those positions add up to t. Return these indices, ensuring each index is used only once. There is exactly one valid solution.

Note: We will assume that the array is zero-indexed and the output order doesn’t matter.

Examples#

Given arr = [2, 7, 11, 15], t = 9
return [0, 1] because arr[0] + arr[1] = 2 + 7 = 9
Given arr = [3, 2, 4], t = 6
return [1, 2] because arr[1] + arr[2] = 2 + 4 = 6

Try it yourself#

Implement your solution in the following coding playground:

def two_sum(arr, t):
# Write your code here
return []

Time complexity: O(n)O(n)

Note: In the worst case, the time complexity for operations like accessing and inserting elements in a dictionary can degrade to O(n)O(n), which could potentially result in an overall time complexity of O(n2)O(n^2) if there are many collisions. However, such scenarios are uncommon.

Space complexity: O(n)O(n)

Solution summary#

The two_sum function efficiently finds the indices of two numbers in an array that sum up to a given target value t. Here’s an overview of the solution:

  • Dictionary for indices storage: The function initializes an empty dictionary indices to store the indices of array elements.

  • Iterative search for complement: It iterates through the array using the enumerate function to access both the elements and their indices. For each element num, it calculates its complement (t - num) and checks if this complement exists in the indices dictionary.

  • Return indices if complement found: If the complement is found in the dictionary, it means that the current element, along with its complement, sums up to the target value. The function returns the indices of these two elements.

  • Update indices dictionary: If the complement is not found, the current element’s index is stored in the indices dictionary with the element itself as the key.

  • Handling no solution: If no solution is found after iterating through the entire array, the function returns an empty list.

4. Delete node with a given key#

We are given the head of a linked list and a key. We have to delete the node that contains this given key.

Examples#

Input: head = [A, B, C, D], key = A
Output: [B, C, D]
Input: head = [B, C, D], key = C
Output: [B, D]
Input: head = [B, D], key = D
Output: [B]

Try it yourself#

Implement your solution in the following coding playground:

main.py
linked_list.py
from linked_list import ListNode
def delete_node(head, key):
# Write your code here
return head
if __name__ == "__main__":
# Test the LinkedList
linked_list = ListNode()
linked_list.append('A')
linked_list.append('B')
linked_list.append('C')
linked_list.append('D')
linked_list.print_list()
print("Deleting the A node")
linked_list = delete_node(linked_list, 'A')
linked_list.print_list()
print("Deleting the C node")
linked_list = delete_node(linked_list, 'C')
linked_list.print_list()
print("Deleting the D node")
linked_list = delete_node(linked_list, 'D')
linked_list.print_list()

Time complexity: O(n)O(n)

Space complexity: O(1)O(1)

Solution summary#

The delete_node function aims to remove a node with a specified key from a singly linked list. Here’s a summary of the solution:

  • Check if the head node contains the value passed in the variable key.

  • Otherwise, traverse the linked list using the pointers prev_node and curr_node to point to two consecutive nodes.

  • While traversing, compare the value of each node with the given key. If a node with the key value is found, update the next pointer of the previous node (prev_node) to bypass the current node (curr_node), effectively removing curr_node from the linked list.

  • Return the head of the modified linked list.

5. Copy linked list with arbitrary pointer#

You are given a linked list where each node has two pointers. The first is the regular next pointer. The second pointer is called arbitrary 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 (inserting, modifying and removing) should not affect the copied list.

Example#

Input: [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
Output: [[7, null], [13,0], [11, 4], [10, 2], [1, 0]]

Try it yourself#

Implement your solution in the following coding playground:

class LinkedListNode:
def __init__(self, data):
self.data = data
self.next = None
self.arbitrary = None
def deep_copy_with_arbitrary_pointer(head):
# Write your code here
return None
def print_linked_list(head):
current = head
while current:
arbitrary_data = current.arbitrary.data if current.arbitrary else None
print(f"Data: {current.data}, Arbitrary data: {arbitrary_data}, Memory address: {id(current)}")
current = current.next
# Example usage with provided input and output
if __name__ == "__main__":
# Create the original linked list with arbitrary pointers
head = LinkedListNode(7)
node1 = LinkedListNode(13)
node2 = LinkedListNode(11)
node3 = LinkedListNode(10)
node4 = LinkedListNode(1)
head.next = node1
node1.next = node2
node2.next = node3
node3.next = node4
head.arbitrary = None
node1.arbitrary = head
node2.arbitrary = node4
node3.arbitrary = node2
node4.arbitrary = head
print("Original linked list: ")
print_linked_list(head)
# Perform deep copy with arbitrary pointer
copied_head = deep_copy_with_arbitrary_pointer(head)
print("\nCopied linked list for verification: ")
print_linked_list(copied_head)

Time complexity: O(n)O(n)

Space complexity: O(n)O(n)

Solution summary#

To perform a deep copy of a linked list with arbitrary pointers, the algorithm follows these steps:

  • Create a mapping: Iterates through the original linked list (head) and creates a mapping (mapping) between each original node and its corresponding new node. Each new node is initialized with the same data as the original node.
  • Set pointers: While iterating through the original list again, it updates the next pointers of the new nodes based on the mapping of original next pointers. Simultaneously, it sets the arbitrary pointers of the new nodes using the mapping to find corresponding new nodes for the arbitrary pointers of the original nodes.
  • Return the head of the new list: Finally, it returns the head of the new list (mapping[head]), which now represents a deep copy of the original linked list with correct next and arbitrary pointers.

6. Mirror binary tree nodes#

Given the root node of a binary tree, swap the ‘left’ and ‘right’ children for each node. The example below shows how the mirrored binary tree would look like for a given binary tree.

Example#

A binary tree and its mirrored tree
A binary tree and its mirrored tree

Try it yourself#

Implement your solution in the following coding playground:

class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def print_tree(self, level=0):
if self.right:
self.right.print_tree(level + 1)
print(' ' * 6 * level+ ' ', self.val)
if self.left:
self.left.print_tree(level + 1)
def mirror_tree(root):
# Write your code here
return root
# Test the function with the provided example
if __name__ == "__main__":
# Define the tree according to the provided example
root = TreeNode(40)
root.left = TreeNode(100)
root.right = TreeNode(400)
root.left.left = TreeNode(150)
root.left.right = TreeNode(50)
root.right.left = TreeNode(300)
root.right.right = TreeNode(600)
# Print the original tree
print("Original Tree:")
root.print_tree()
# Perform mirror operation
mirrored_root = mirror_tree(root)
# Print the mirrored tree
print("\nMirrored Tree:")
mirrored_root.print_tree()

Time complexity: O(n)O(n)

Space complexity: O(n)O(n)

Solution summary#

The algorithm for mirroring binary tree nodes follows these steps:

  • Base case: If the root is None, return None.

  • Otherwise, swap the left and right children of the current root node to perform a single mirroring step. Then call the mirror_tree function on the left and right child nodes to mirror the two subtrees recursively.

  • Return the root node.

7. Check if two binary trees are identical#

Given the roots of two binary trees, determine if these trees are identical or not. Identical trees have the same structure and data at each node.

Examples#

Input: 1 1
/ \ / \
2 3 2 3
Output: True
Input: 1 1
/ \ / \
2 1 1 2
Output: False

Try it yourself#

Implement your solution in the following coding playground:

class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def print_tree(self):
self._print_tree(self, 0)
def _print_tree(self, root, depth):
if root:
self._print_tree(root.right, depth + 1)
print(" " * depth + str(root.val))
self._print_tree(root.left, depth + 1)
def identical(root1, root2):
# Write your code here
return None
if __name__ == "__main__":
# Example 1
root1 = TreeNode(1)
root1.left = TreeNode(2)
root1.right = TreeNode(3)
root2 = TreeNode(1)
root2.left = TreeNode(2)
root2.right = TreeNode(3)
print("Tree 1:")
root1.print_tree()
print("Tree 2:")
root2.print_tree()
print("Are trees identical?", identical(root1, root2)) # Output: True
# Example 2
root1 = TreeNode(1)
root1.left = TreeNode(2)
root1.right = TreeNode(1)
root2 = TreeNode(1)
root2.left = TreeNode(1)
root2.right = TreeNode(2)
print("\nTree 1:")
root1.print_tree()
print("Tree 2:")
root2.print_tree()
print("Are trees identical?", identical(root1, root2)) # Output: False

If both roots contain identical values and their left and right subtrees are also identical, return True. Otherwise return False.

Time complexity: O(n)O(n)

Space complexity: O(n)O(n)

Solution summary#

The identical function checks if two binary trees are identical. It follows these steps:

  • Base case: If both roots are None, they are identical, so return True.
  • Recursive case:
    • If one of the roots is None while the other is not, they are not identical, so return False.
    • If both roots contain identical values and their left and right subtrees are also identical, return True. Otherwise return False.

8. String segmentation#

You are given a list of words, word_dict, and a large input string, s. You have to find out whether the input string can be completely segmented into the words of the given word_dict. Note that you are allowed to reuse the same word multiple times from word_dict.

Return True if s can be segmented otherwise, return False.

The following example elaborates the problem further.

Example#

Input: s = "applepenapple", word_dict = ["apple", "pen"]
Output: True
Explanation: Return True because "applepenapple" can be segmented as "apple pen apple".

Try it yourself#

Implement your solution in the following coding playground:

def can_segment_string(s, word_dict):
# Write your code here
return False

Time complexity: O(n2m)O(n^{2}m) (where mm is the size of the dictionary)

Space complexity: O(n)O(n)

Solution summary#

The can_segment_string function efficiently checks if a string can be segmented into words from a given list using dynamic programming. Here’s how it works:

  • Initialization: Determine the length of the input string s (n = len(s)). Initialize a boolean array dp (dp = [False] * (n + 1)) where dp[i] indicates if the substring s[0:i] can be segmented into words from word_dict. Set dp[0] = True to indicate that an empty string can always be segmented.

  • Dynamic programming approach: Iterate over each value of i from 00 to n representing the length of the string being considered for segmentation. Iterate over j from 00 to i-1, representing potential end points of the first segment. Check if dp[j] is True (indicating s[0:j] can be segmented) and s[j:i] exists in word_dict. If true, set dp[i] = True. Break out of the inner loop once a segmentation is found to avoid unnecessary checks.

  • Result: Return dp[n], where dp[n] is True if the entire string s can be segmented into words from word_dict, otherwise False.

9. Find all palindrome substrings#

Given a string find all non-single letter substrings that are palindromes.

Example#

Input: "abacddcbe"
Output: ["aba", "cddc", "dd"]
Explanation: There are three palindromic strings: "aba", "cddc", and "dd".

Try it yourself#

Implement your solution in the following coding playground:

def find_palindrome_substrings(s):
# Write your code here
return None

Time complexity: O(n3)O(n^3)

Space complexity: O(n3)O(n^3)

Solution summary#

Let’s break down the solution step by step:

  • Iterating through substrings: The code starts by iterating through all possible substrings of the input string s. It does this with two nested loops:

    • The outer loop iterates over each character of the string s, denoted by the variable i.
    • The inner loop iterates over each character starting from i+2 to the end of the string s, denoted by the variable j. This ensures that the length of the substring being considered is greater than 11, indicating a non-trivial palindrome.
  • Extracting substrings: For each combination of i and j, the code extracts the substring using slicing: substring = s[i:j].

  • Checking for palindromes: For each extracted substring, the code checks if it’s a palindrome:

    • It does this by comparing the substring with its reverse using substring == substring[::-1].
  • Storing palindromes: If the substring is a palindrome, it’s appended to the list palindromes.

  • Returning palindromes: Finally, the list of palindromes is returned.

10. Largest sum subarray#

Given an array of integers, find the contiguous subarray (containing at least one element) that has the largest sum and return the sum.

Example#

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

Try it yourself#

Implement your solution in the following coding playground:

def max_subarray_sum(nums):
# Write your code here
return

Time complexity: O(n)O(n)

Space complexity: O(1)O(1)

Solution summary#

To determine the largest sum subarray in an array, the algorithm follows these steps:

  • Initialize max_sum and current_sum variables to the first element of the array.
  • Iterate through each element in the input array. At each iteration, update current_sum by taking the maximum between the current element and the sum of the current element and the previous current_sum.
  • Update max_sum by taking the maximum between the current max_sum and the updated current_sum.
  • Finally, return max_sum as the result.

11. Determine if the number is valid#

Given an input string, determine if it represents a valid number or not. For this problem, we assume that a valid number consists of numeric digits (in the range 0–9), an optional sign (+ or -) at the beginning, and an optional decimal point that must be followed by one or more digits.

Examples#

4.325 is a valid number.
1.1.1 is NOT a valid number.
222 is a valid number.
22. is NOT a valid number.
0.1 is a valid number.
22.22. is NOT a valid number.

Try it yourself#

Implement your solution in the following coding playground:

class STATE:
START, INTEGER, DECIMAL, UNKNOWN, AFTER_DECIMAL = range(5)
def get_next_state(current_state, ch):
# Write your code here
pass
def is_number_valid(s):
# Write your code here
return None

Time complexity: O(n)O(n)

Space complexity: O(1)O(1)

Solution summary#

To determine if a given string represents a valid number, the algorithm follows these steps:

  • If the input string is empty, return False.

  • Initialize an index variable i to track the position in the string.

  • If the first character is a positive or negative sign (’+’ or ‘-’), increment i to skip it.

  • Define a state variable current_state to track the state of parsing the string.

  • Iterate through the characters of the string using index i.

  • Update current_state based on the current character using the get_next_state function.

  • If current_state becomes UNKNOWN at any point, return False, indicating an invalid number.

  • If the final state is DECIMAL, return False, as the number cannot end with a decimal point.

  • If all characters are successfully parsed and no invalid states are encountered, return True, indicating a valid number.

12. Print balanced brace combinations#

Given a positive integer nn, print all strings containing nn pairs of braces that are balanced.

Example#

For example, given n = 3, a solution set is:
[
"{{{}}}",
"{{}{}}",
"{{}}{}",
"{}{{}}",
"{}{}{}"
]

Try it yourself#

Implement your solution in the following coding playground:

def generateBraces(n):
# Write your code here
return []

Time complexity: O(22n)O(2^{2n})

Space complexity: O(n)O(n)

Solution summary#

To generate all balanced brace combinations for a given value in variable n, the algorithm follows these steps:

  • Start with an empty result list.

  • Initialize a stack with a tuple containing a string '{', and counters left and right, both initially set to 1 and 0 respectively.

  • While the stack is not empty:

    • Pop the top tuple from the stack, and store its three elements in the variables s, left, and right.
    • If the length of s reaches 2*n and left equals right, append s to the result list.
    • Otherwise, if left is less than n, push a tuple onto the stack with s appended with a left brace, and the left variable incremented by 11.
    • Also, if right is less than left, push a tuple onto the stack with s appended with a right brace, and right incremented by 11.
  • Return the result list containing all balanced brace combinations.

13. LRU#

Least Recently Used (LRU) is a common caching strategy. It defines the policy to evict elements from the cache to make room for new elements when the cache is full, meaning it discards the least recently used items first.

Example#

# Initialize the cache with capacity 2
cache = LRUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1)) # returns 1
cache.put(3, 3) # evicts key 2
print(cache.get(2)) # returns -1 (not found)
cache.put(4, 4) # evicts key 1
print(cache.get(1)) # returns -1 (not found)
print(cache.get(3)) # returns 3
print(cache.get(4)) # returns 4
LRUCache cache = new LRUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); # returns 1
cache.put(3, 3); # evicts key 2
cache.get(2); # returns -1 (not found)
cache.put(4, 4); # evicts key 1
cache.get(1); # returns -1 (not found)
cache.get(3); # returns 3
cache.get(4); # returns 4

Try it yourself#

Implement your solution in the following coding playground:

class ListNode:
def __init__(self, key=None, value=None):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = {}
self.head = ListNode()
self.tail = ListNode()
self.head.next = self.tail
self.tail.prev = self.head
def _add_node(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def _remove_node(self, node):
prev = node.prev
next_node = node.next
prev.next = next_node
next_node.prev = prev
def _move_to_head(self, node):
self._remove_node(node)
self._add_node(node)
def get(self, key):
# Write your code here
return -1
def set(self, key, value):
# Write your code here
pass

Time complexity: O(1)O(1)

Space complexity: O(1)O(1)

Solution summary#

To implement the LRU cache, we utilize a combination of a doubly linked list and a dictionary:

  • We define a ListNode class to represent individual nodes in a doubly linked list. Each node contains key-value pairs and pointers to the previous and next nodes.

  • We implement the LRUCache class, which is initialized with a given capacity. It also maintains a dictionary (cache) to store key-node mappings and doubly linked list nodes to maintain the order of recently used items.

  • The _add_node method adds a node to the head of the linked list.

  • The _remove_node method removes a given node from the linked list.

  • The _move_to_head method moves a given node to the head of the linked list, indicating it as the most recently used.

  • The get method retrieves the value associated with a given key. If the key exists in the cache, it moves the corresponding node to the head of the linked list and returns the value stored in it; otherwise, it returns -1.

  • The set method sets the value associated with a given key. If the key exists in the cache, it updates the value and moves the corresponding node to the head of the linked list. If the key does not exist in the cache and the cache is at full capacity, it removes the least recently used node from the tail of the linked list and the cache dictionary. Then, it adds a new node with the given key and value to the head of the linked list and the cache dictionary.

14. Find Low/High Index#

Given an array of integers arr sorted in ascending order, find the starting and ending position of a given target value (key). If the target is not found in the array, return [-1, -1].

Example#

Input: arr = [5, 7, 7, 8, 8, 10], key = 8
Output: [3, 4]

Try it yourself#

Implement your solution in the following coding playground:

def find_low_index(arr, key):
# Write your code here
return -1
def find_high_index(arr, key):
# Write your code here
return -1
def find_low_high_index(arr, key):
# Write your code here
return []

Time complexity: O(logn)O(\log n)

Space complexity: O(1)O(1)

Solution summary#

The solution consists of two primary functions: find_low_index and find_high_index. These functions utilize a binary search algorithm to determine the starting and ending positions of a given target value in a sorted array.

  • find_low_index(arr, key): This function identifies the lowest index of the target value key in the sorted array arr. It employs binary search to locate the leftmost occurrence of the target value.

  • find_high_index(arr, key): Similar to find_low_index, this function determines the highest index of the target value key in the sorted array arr. It utilizes binary search to find the rightmost occurrence of the target value.

  • find_low_high_index(arr, key): This function integrates the results from find_low_index and find_high_index to return a list containing the low and high indices of the target value key in the sorted array arr.

15. Merge overlapping intervals#

Given a collection of intervals, merge all overlapping intervals.

Examples#

Example 1:
Input: [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6].
Example 2:
Input: [[1,4],[4,5]]
Output: [[1,5]]
Explanation: Intervals [1,4] and [4,5] are considered overlapping.

Try it yourself#

Implement your solution in the following coding playground:

def merge_intervals(intervals):
# Write your code here
return []

Time complexity: O(nlogn)O(n \log n)

Space complexity: O(n)O(n)

Solution summary#

To merge overlapping intervals, the algorithm follows these steps:

  • Sort the intervals based on their start time.

  • Initialize an empty list to store merged intervals and add the first interval from the sorted list.

  • Iterate through the sorted intervals starting from the second interval.

  • If the current interval overlaps with the previous one, merge them by updating the end time of the previous interval to the maximum of both end times.

  • If there’s no overlap, add the current interval to the merged list.

  • Return the merged list of intervals as the result.

16. Path sum#

Given a binary tree and a target, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given target.

Example#

Given the binary tree below and target = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
return True, as there exist a root-to-leaf path 5->4->11->2 whose path sum is 22.

Try it yourself#

Implement your solution in the following coding playground:

class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def print_tree(self, level=0):
if self.right:
self.right.print_tree(level + 1)
print(" " * level + str(self.val))
if self.left:
self.left.print_tree(level + 1)
def has_path_sum(root, target):
# Write your code here
return None
if __name__ == "__main__":
# Create the binary tree
root = TreeNode(5)
root.left = TreeNode(4)
root.right = TreeNode(8)
root.left.left = TreeNode(11)
root.left.left.left = TreeNode(7)
root.left.left.right = TreeNode(2)
root.right.left = TreeNode(13)
root.right.right = TreeNode(4)
root.right.right.right = TreeNode(1)
target = 22
# Check if there exists a root-to-leaf path with the given sum
result = has_path_sum(root, target)
# Output the result
print("Does there exist a root-to-leaf path with sum {}?".format(target))
print("Result:", result)
root.print_tree()

Time complexity: O(n)O(n)

Space complexity: O(n)O(n)

Solution summary#

To determine if there exists a path from the root to a leaf node in a binary tree such that the sum of values along the path equals a given target, the algorithm follows these steps:

  • Define a recursive function has_path_sum(root, target_sum) to traverse the binary tree.

  • If the root is None there is no path that sums to the given target. So, return False.

  • If the root is a leaf node (i.e., it has no left or right child) and if its value equals the target sum, return True.

  • Recursively call the function on the left and right subtrees, subtracting the value of the current node from the target.

  • If either recursive call returns True, indicating a path with the target exists in either subtree, return True.

  • If neither subtree contains a path with the target, return False.

17. Find the missing number#

We are given an array containing nn distinct numbers taken from the range 00 to nn. Since the array has only nn numbers out of the total n+1n+1 numbers, find the missing number.

Examples#

Example 1:
Input: [4, 0, 3, 1]
Output: 2
Example 2:
Input: [8, 3, 5, 2, 4, 6, 0, 1]
Output: 7

Try it yourself#

Implement your solution in the following coding playground:

def find_missing_number(nums):
# Write your code here
return -1

Time complexity: O(n)O(n)

Space complexity: O(1)O(1)

Solution summary#

To find the missing number in an array, the algorithm follows these steps:

  • Calculate the expected sum of the numbers from 00 to nn using the formula n×(n+1)2\frac{n \times (n + 1)}{2} , where nn is the length of the input array.

  • Calculate the actual sum of the numbers in the input array using the sum function.

  • The missing number is the difference between the expected sum and the actual sum.

  • Return the missing number as the result.

18. Reverse a linked list#

Reverse a singly linked list. See the example below.

Example#

Input: 1 -> 2 -> 3 -> 4 -> 5 -> NULL
Output: 5 -> 4 -> 3 -> 2 -> 1 -> NULL

Try it yourself#

Implement your solution in the following coding playground:

main.py
linked_list.py
from linked_list import ListNode, create_linked_list, print_linked_list
def reverse_linked_list(head):
# Write your code here
return head
if __name__ == "__main__":
# Create a linked list
values = [1, 2, 3, 4, 5]
head = create_linked_list(values)
# Print the original linked list
print("Original linked list:")
print_linked_list(head)
# Reverse the linked list
reversed_head = reverse_linked_list(head)
# Print the reversed linked list
print("\nReversed linked list:")
print_linked_list(reversed_head)

Time complexity: O(n)O(n)

Space complexity: O(1)O(1)

Solution summary#

To reverse a linked list, the algorithm follows these steps:

  • Initialize two pointers, prev and current, to None and the head of the linked list, respectively.
  • Iterate through the linked list, and at each step, store the next node of the current node in a temporary variable next_node.
  • Update the next pointer of the current node to point to the previous node (prev).
  • To prepare for the next round, update prev to be the current node and current to be the node stored in the temporary variable next_node.
  • After the last iteration, the prev pointer points to the new head of the reversed linked list.
  • Return the head of the reversed linked list (prev).

More practice and preparation#

Congratulations on finishing these problems! To pass the coding interview, it’s important that you continue practice. To prepare for the Google Coding Interview, you should brush up on dynamic programming, backtracking, recursion, greedy algorithms, and divide & conquer. Here are some more common coding interview questions to practice.

  1. Determine if the sum of three integers is equal to the given value

  2. Intersection point of two linked lists

  3. Move zeros to the left

  4. Add two integers

  5. Merge two sorted linked lists

  6. Convert binary tree to doubly linked list

  7. Level order traversal of binary tree

  8. Reverse words in a sentence

  9. Find maximum single sell profit

  10. Calculate the power of a number

  1. Find all possible subsets

  2. Clone a directed graph

  3. Serialize / deserialize binary tree

  4. Search rotated array

  5. Set columns and rows as zeros

  6. Connect all siblings

  7. Find all sum combinations

  8. Clone a directed graph

  9. Closest meeting point

  10. Search for the given key in a 2D matrix



Wrapping up and resources#

Cracking the Google coding interview simply comes down to repetition and practice. It’s important that you continue practice more interview questions after reading this article.

To help you prepare for Google interviews, Educative has created these unique language-specific courses:

  • Grokking Coding Interview Patterns in Python
  • Grokking Coding Interview Patterns in JavaScript
  • Grokking Coding Interview Patterns in Java
  • Grokking Coding Interview Patterns in Go
  • Grokking Coding Interview Patterns in C++
  • Available in multiple languages, these courses will give you a hands-on mastery of the underlying patterns to help you solve any coding interview problem.

    This is coding interview prep reimagined, with your needs in mind.

    Happy learning!


    Continue reading about coding interview prep#

    Frequently Asked Questions

    Is LeetCode enough for Google?

    While LeetCode can be a useful tool to prepare candidates for interview prep, more is needed to ace the interview. Candidates must focus on logical thinking, in-depth knowledge of foundational concepts, and communication skills.


      

    Free Resources