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.
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:
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:
Design a class to efficiently find the 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 .
The class should expose a function add(int value)
which will add the number value
to the stream of numbers, and will return the largest number in the stream seen so far.
Input: [4, 1, 3, 12, 7, 14], k = 3Calling add(6) should return '7'.Calling add(13) should return '12'.Calling add(4) should still return '12'.
Implement your solution in the following coding playground:
import heapqclass KthLargest:# Constructor to initialize heap and add values in itdef __init__(self, k, nums):pass# Adds element in the heap and return the Kth largestdef add(self, val):# Write your code herereturn -1
Time complexity of __init__
:
Time complexity of add
: , where represents the size of the heap.
Space complexity of __init__
:
Space complexity of add
: , where represents the size of the heap.
The KthLargest
class is designed to efficiently maintain the 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 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 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 largest element.
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.
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.Input: nums = [1, 2, 3, 4, 5], k = 4, target = 3Output: [1, 2, 3, 4]Input: nums = [2, 4, 5, 6, 9], k = 3, target = 6Output: [4, 5, 6]
Implement your solution in the following coding playground:
def binary_search(array, target):left = 0right = len(array) - 1while left <= right:mid = (left + right) // 2if array[mid] == target:return midif array[mid] < target:left = mid + 1else:right = mid - 1return left
Time complexity:
Space complexity:
The find_closest_elements
function efficiently identifies the 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 elements. Likewise, if the target is greater than or equal to the last element of nums
, it returns the last 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 closest elements. It iteratively adjusts the window boundaries based on the proximity of elements to the target until the window contains exactly elements.
Result extraction: Finally, the function returns the elements within the determined window, representing the closest numbers to the target.
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.
Given arr = [2, 7, 11, 15], t = 9return [0, 1] because arr[0] + arr[1] = 2 + 7 = 9Given arr = [3, 2, 4], t = 6return [1, 2] because arr[1] + arr[2] = 2 + 4 = 6
Implement your solution in the following coding playground:
def two_sum(arr, t):# Write your code herereturn []
Time complexity:
Note: In the worst case, the time complexity for operations like accessing and inserting elements in a dictionary can degrade to , which could potentially result in an overall time complexity of if there are many collisions. However, such scenarios are uncommon.
Space complexity:
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.
We are given the head of a linked list and a key. We have to delete the node that contains this given key.
Input: head = [A, B, C, D], key = AOutput: [B, C, D]Input: head = [B, C, D], key = COutput: [B, D]Input: head = [B, D], key = DOutput: [B]
Implement your solution in the following coding playground:
from linked_list import ListNodedef delete_node(head, key):# Write your code herereturn headif __name__ == "__main__":# Test the LinkedListlinked_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:
Space complexity:
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.
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.
Input: [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]Output: [[7, null], [13,0], [11, 4], [10, 2], [1, 0]]
Implement your solution in the following coding playground:
class LinkedListNode:def __init__(self, data):self.data = dataself.next = Noneself.arbitrary = Nonedef deep_copy_with_arbitrary_pointer(head):# Write your code herereturn Nonedef print_linked_list(head):current = headwhile current:arbitrary_data = current.arbitrary.data if current.arbitrary else Noneprint(f"Data: {current.data}, Arbitrary data: {arbitrary_data}, Memory address: {id(current)}")current = current.next# Example usage with provided input and outputif __name__ == "__main__":# Create the original linked list with arbitrary pointershead = LinkedListNode(7)node1 = LinkedListNode(13)node2 = LinkedListNode(11)node3 = LinkedListNode(10)node4 = LinkedListNode(1)head.next = node1node1.next = node2node2.next = node3node3.next = node4head.arbitrary = Nonenode1.arbitrary = headnode2.arbitrary = node4node3.arbitrary = node2node4.arbitrary = headprint("Original linked list: ")print_linked_list(head)# Perform deep copy with arbitrary pointercopied_head = deep_copy_with_arbitrary_pointer(head)print("\nCopied linked list for verification: ")print_linked_list(copied_head)
Time complexity:
Space complexity:
To perform a deep copy of a linked list with arbitrary pointers, the algorithm follows these steps:
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.mapping[head]
), which now represents a deep copy of the original linked list with correct next and arbitrary pointers.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.
Implement your solution in the following coding playground:
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef 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 herereturn root# Test the function with the provided exampleif __name__ == "__main__":# Define the tree according to the provided exampleroot = 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 treeprint("Original Tree:")root.print_tree()# Perform mirror operationmirrored_root = mirror_tree(root)# Print the mirrored treeprint("\nMirrored Tree:")mirrored_root.print_tree()
Time complexity:
Space complexity:
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_tre
e function on the left and right child nodes to mirror the two subtrees recursively.
Return the root
node.
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.
Input: 1 1/ \ / \2 3 2 3Output: TrueInput: 1 1/ \ / \2 1 1 2Output: False
Implement your solution in the following coding playground:
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef 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 herereturn Noneif __name__ == "__main__":# Example 1root1 = 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 2root1 = 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:
Space complexity:
The identical
function checks if two binary trees are identical. It follows these steps:
None
, they are identical, so return True
.None
while the other is not, they are not identical, so return False
.True
. Otherwise return False
.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.
Input: s = "applepenapple", word_dict = ["apple", "pen"]Output: TrueExplanation: Return True because "applepenapple" can be segmented as "apple pen apple".
Implement your solution in the following coding playground:
def can_segment_string(s, word_dict):# Write your code herereturn False
Time complexity: (where is the size of the dictionary)
Space complexity:
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 to n
representing the length of the string being considered for segmentation. Iterate over j
from 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
.
Given a string find all non-single letter substrings that are palindromes.
Input: "abacddcbe"Output: ["aba", "cddc", "dd"]Explanation: There are three palindromic strings: "aba", "cddc", and "dd".
Implement your solution in the following coding playground:
def find_palindrome_substrings(s):# Write your code herereturn None
Time complexity:
Space complexity:
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:
s
, denoted by the variable
i
.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 , 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:
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.
Given an array of integers, find the contiguous subarray (containing at least one element) that has the largest sum and return the sum.
Input: [-2,1,-3,4,-1,2,1,-5,4],Output: 6Explanation: [4,-1,2,1] has the largest sum = 6.
Implement your solution in the following coding playground:
def max_subarray_sum(nums):# Write your code herereturn
Time complexity:
Space complexity:
To determine the largest sum subarray in an array, the algorithm follows these steps:
max_sum
and current_sum
variables to the first element of the array.current_sum
by taking the maximum between the current element and the sum of the current element and the previous current_sum
.max_sum
by taking the maximum between the current max_sum
and the updated current_sum
.max_sum
as the result.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.
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.
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 herepassdef is_number_valid(s):# Write your code herereturn None
Time complexity:
Space complexity:
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.
Given a positive integer , print all strings containing pairs of braces that are balanced.
For example, given n = 3, a solution set is:["{{{}}}","{{}{}}","{{}}{}","{}{{}}","{}{}{}"]
Implement your solution in the following coding playground:
def generateBraces(n):# Write your code herereturn []
Time complexity:
Space complexity:
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:
s
, left
, and right
.s
reaches
2*n
and left
equals right
, append s
to the result list.left
is less than
n
, push a tuple onto the stack with s
appended with a left brace, and the left
variable incremented by .right
is less than left
, push a tuple onto the stack with s
appended with a right brace, and right
incremented by .Return the result list containing all balanced brace combinations.
# Initialize the cache with capacity 2cache = LRUCache(2)cache.put(1, 1)cache.put(2, 2)print(cache.get(1)) # returns 1cache.put(3, 3) # evicts key 2print(cache.get(2)) # returns -1 (not found)cache.put(4, 4) # evicts key 1print(cache.get(1)) # returns -1 (not found)print(cache.get(3)) # returns 3print(cache.get(4)) # returns 4LRUCache cache = new LRUCache( 2 /* capacity */ );cache.put(1, 1);cache.put(2, 2);cache.get(1); # returns 1cache.put(3, 3); # evicts key 2cache.get(2); # returns -1 (not found)cache.put(4, 4); # evicts key 1cache.get(1); # returns -1 (not found)cache.get(3); # returns 3cache.get(4); # returns 4
Implement your solution in the following coding playground:
class ListNode:def __init__(self, key=None, value=None):self.key = keyself.value = valueself.prev = Noneself.next = Noneclass LRUCache:def __init__(self, capacity):self.capacity = capacityself.cache = {}self.head = ListNode()self.tail = ListNode()self.head.next = self.tailself.tail.prev = self.headdef _add_node(self, node):node.prev = self.headnode.next = self.head.nextself.head.next.prev = nodeself.head.next = nodedef _remove_node(self, node):prev = node.prevnext_node = node.nextprev.next = next_nodenext_node.prev = prevdef _move_to_head(self, node):self._remove_node(node)self._add_node(node)def get(self, key):# Write your code herereturn -1def set(self, key, value):# Write your code herepass
Time complexity:
Space complexity:
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.
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]
.
Input: arr = [5, 7, 7, 8, 8, 10], key = 8Output: [3, 4]
Implement your solution in the following coding playground:
def find_low_index(arr, key):# Write your code herereturn -1def find_high_index(arr, key):# Write your code herereturn -1def find_low_high_index(arr, key):# Write your code herereturn []
Time complexity:
Space complexity:
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
.
Given a collection of intervals, merge all overlapping intervals.
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.
Implement your solution in the following coding playground:
def merge_intervals(intervals):# Write your code herereturn []
Time complexity:
Space complexity:
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.
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.
Given the binary tree below and target = 22,5/ \4 8/ / \11 13 4/ \ \7 2 1return True, as there exist a root-to-leaf path 5->4->11->2 whose path sum is 22.
Implement your solution in the following coding playground:
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef 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 herereturn Noneif __name__ == "__main__":# Create the binary treeroot = 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 sumresult = has_path_sum(root, target)# Output the resultprint("Does there exist a root-to-leaf path with sum {}?".format(target))print("Result:", result)root.print_tree()
Time complexity:
Space complexity:
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
.
We are given an array containing distinct numbers taken from the range to . Since the array has only numbers out of the total numbers, find the missing number.
Example 1:Input: [4, 0, 3, 1]Output: 2Example 2:Input: [8, 3, 5, 2, 4, 6, 0, 1]Output: 7
Implement your solution in the following coding playground:
def find_missing_number(nums):# Write your code herereturn -1
Time complexity:
Space complexity:
To find the missing number in an array, the algorithm follows these steps:
Calculate the expected sum of the numbers from to using the formula , where 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.
Reverse a singly linked list. See the example below.
Input: 1 -> 2 -> 3 -> 4 -> 5 -> NULLOutput: 5 -> 4 -> 3 -> 2 -> 1 -> NULL
Implement your solution in the following coding playground:
from linked_list import ListNode, create_linked_list, print_linked_listdef reverse_linked_list(head):# Write your code herereturn headif __name__ == "__main__":# Create a linked listvalues = [1, 2, 3, 4, 5]head = create_linked_list(values)# Print the original linked listprint("Original linked list:")print_linked_list(head)# Reverse the linked listreversed_head = reverse_linked_list(head)# Print the reversed linked listprint("\nReversed linked list:")print_linked_list(reversed_head)
Time complexity:
Space complexity:
To reverse a linked list, the algorithm follows these steps:
prev
and current
, to None
and the head of the linked list, respectively.next_node
.next
pointer of the current node to point to the previous node (prev
).prev
to be the current node and current
to be the node stored in the temporary variable next_node
.prev
pointer points to the new head of the reversed linked list.prev
).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.
Determine if the sum of three integers is equal to the given value
Intersection point of two linked lists
Move zeros to the left
Add two integers
Merge two sorted linked lists
Convert binary tree to doubly linked list
Level order traversal of binary tree
Reverse words in a sentence
Find maximum single sell profit
Calculate the power of a number
Find all possible subsets
Clone a directed graph
Serialize / deserialize binary tree
Search rotated array
Set columns and rows as zeros
Connect all siblings
Find all sum combinations
Clone a directed graph
Closest meeting point
Search for the given key in a 2D matrix
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:
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!
Free Resources