Algorithm-based questions are a staple of any modern coding interview, as they demonstrate your problem-solving and critical thinking skills. To make sure you don’t get caught off guard in your next Java interview, we’ve put together 15 of the most common algorithm coding questions used by most tech companies and recruiters across the industry.
These algorithm coding questions vary in difficulty, so if you can’t figure out one don’t be ashamed to move on to the next and return later. With enough practice, you’ll shortly be able to crack any interview question thrown at you. Each question is followed by a detailed explanation to help you get prepped for the big interviews ahead.
Dive into 50+ Java algorithm questions, each with hands-on practice and explanation.
Using Big O notation is a foundational skill that will be checked by Java interviewers. Before we jump into some more intensive examples, we’ll first go through some questions that test your knowledge and understanding of Big O notation.
As a refresher, Big O is used to classify algorithms based on how their run-time and space requirements grow with input size.
Problem Statement:
Compute the Big O complexity of the code snippet given below.
Try it first on your own, but check our solution if you get stuck.
class NestedLoop { public static void main(String[] args) { int n = 10; int sum = 0; double pie = 3.14; for (int var = 1; var < n; var = var + 3) { System.out.println("Pie: " + pie); for (int j = 1; j < n; j = j + 2) { sum++; } System.out.println("Sum = " + sum); } } }
Solution Breakdown:
The first for loop on line 7 can be broken down into 3 parts:
Since the initialization (int var = 0)
only happens once in the entire program, it takes one unit of time. The comparison (var < n
) gets executed $(\frac n3 + 1)$ times and the increment runs $\frac n3$ times.
Similarly, (int j = 0
) runs $\frac n3$ times, the comparison (j < n
) runs $\frac n3 * (\frac n2 + 1)$, and the increment (j = j + 2
) gets executed $\frac n2$ times for each iteration of the outer loop, so it runs:
$\frac n3 * \frac n2 = \frac {n^2}6$
Below is the line-by-line calculation of time complexity:
Finally, we add all lines’ time complexity, drop the leading constants and lower order terms, and find our Big O Complexity.
Almost ever interviewer will ask a question which calls for at least one type of searching or sorting, if not more. To help you prepare for these questions, we’ve included the following overview section to build foundational search/sort algorithm proficiency.
Note: It’s unlikely you’ll be prompted to use a certain algorithm in an interview. Instead, you must learn to recognize which algorithm to use based on keywords in the problem statement.
As you practice, try to pinpoint which part of the problem statement would lead you to use the indicated algorithm.
Problem Statement:
Given an unsorted array of numbers, find K
th smallest number in it.
Please note that it is the K
th smallest number in the sorted order, not the K
th distinct element.
import java.util.*; class KthSmallestNumber { public static int findKthSmallestNumber(int[] nums, int k) { // TODO: Write your code here return -1; } public static void main(String[] args) { int result = KthSmallestNumber.findKthSmallestNumber(new int[] { 1, 5, 12, 2, 11, 5 }, 3); System.out.println("Kth smallest number is: " + result); // since there are two 5s in the input array, our 3rd and 4th smallest numbers should be a '5' result = KthSmallestNumber.findKthSmallestNumber(new int[] { 1, 5, 12, 2, 11, 5 }, 4); System.out.println("Kth smallest number is: " + result); result = KthSmallestNumber.findKthSmallestNumber(new int[] { 5, 12, 11, -1, 12 }, 3); System.out.println("Kth smallest number is: " + result); } }
Solution Breakdown:
Time Complexity: average $O(N)$ or worst case $O(N^2)$
We use Quicksort’s partitioning scheme to find the Kth smallest number. We recursively partition the input array and if, after partitioning, our pivot is at the K-1
index we have found our required number. If not, we choose one the following options:
Problem Statement:
We are given a 2D array where all elements in any individual row or column are sorted. In such a matrix, we have to search or find the position of, a given key.
class searchMatrix{ public static IntPair search_in_matrix(int matrix[][], int value) { //TODO: Write - Your - Code return new IntPair(-1, -1); } }
Solution Breakdown:
Time Complexity: $O(m + n)$
We start from the upper right corner of the matrix and compare its value with the key. If they are equal, we have found the position of the key.
If the key is smaller than the current element, we move to the left one position. If the key is larger than the current element, we move right one position.
As the matrix is sorted, moving left always results in lower values than the current while moving down always results higher values. We continue this process until either we find the element or go out of the boundary of the matrix (which indicates that the key does not exist).
Problem Statement:
Given the head pointer of a linked sort, sort the linked list in ascending order using merge sort, and return the new head pointer of the sorted linked list.
class MergeSort{ public static LinkedListNode mergeSort(LinkedListNode head) { //TODO: Write - Your - Code return head; } }
Solution Breakdown:
Time Complexity: $O(nlogn)$
In the dividing step, we split our input linked list into two halves and keep doing so until there is a linked list of size 1 or 0. Linked lists of size 1 and 0 are always sorted. In the combining step, we merge sorted lists and keep doing so until we have a completely sorted list.
At each step, we divide our problem into two sub-problems. The size of each sub-problem is $\frac n2$ and the total cost of combining steps (merging sorted lists) is $n$.
Problem Statement:
Given the head pointer of a linked list, sort the linked list in ascending order using insertion sort. Return the new head pointer of the sorted linked list.
class InsertionSort{ public static LinkedListNode insertionSort(LinkedListNode head) { //TODO: Write - Your - Code return head; } }
Solution Breakdown:
Time Complexity: $O(n^2)$
While the original list is not empty:
Remove an element (say ‘X’) from the original list.
Insert ‘X’ at the correct sorted position in the sorted list.
To insert a node into the sorted linked list, we may need to scan the entire sorted list depending upon the node being inserted.
Problem Statement:
Using a HashMap, implement a function that takes an array arr
, a number value
, and the size of the array as an input and returns two numbers that add up to value
.
class CheckSum { public static int[] findSum(int[] arr, int n) { int[] result = new int[2]; // write your code here return result; // return the elements in the array whose sum is equal to the value passed as parameter } }
Solution Breakdown:
Time Complexity: $O(n)$
For all the elements in the arr
array, we store the difference n - arr[i]
in hmap
.
Then with another iteration over arr
, we check if any element of arr
exists in the hmap
, which means the difference of n
and the number found (n - arr[i]
) are also present.
Therefore, an array of size 2 called result
is created to store the pair that sums up to n
. If hmap
contains an array element, result[]
is updated, or else it is returned containing the default value.
Problem Statement:
Implement an isSubset()
function to take two arrays as input and check whether an array is a subset of another given array.
class CheckSubset { public static boolean isSubset(int[] arr1, int[] arr2) { // write your code here return false; } }
Solution Breakdown:
Time Complexity: $O(m+n)$
First, we iterate over arr2
and arr3
to see whether their elements can be found in arr1
.
At the back end, the values are checked against their hashed indices in arr1
.
Dynamic Programming is a central algorithm technique for the modern developer, as it focuses on breaking a problem into simpler sub-problems to achieve optimization. The more optimal the solution to sub-problems, the more optimal the overall solution is.
This is the foundation of recursive problem-solving and therefore will be asked by any good interviewer.
Dynamic Programming questions can either be solved from a Top-Down approach or a Bottom-Up approach, using either Memoization or Tabulation, respectively. Interviewers may ask for one or may leave it to your decision.
Below we’ll see an example of each so you’re prepared for any alternative.
Problem Statement:
Imagine that you’re an adventurer with a knapsack looking over a dragon’s hoard.
Given two integer arrays that represent the weights and profits of N
items, implement a function knapSack()
that finds a subset of these items that will give us the maximum profit without their cumulative weight exceeding a given number capacity
. Each item may only be selected once, which means when we get to it we can either skip it or put it in the knapsack.
Use the top-down approach with memoization.
class KnapsackProblem { static int Knapsack(int profits[], int profitsLength, int weights[], int weightsLength, int capacity) { // write your code here return -1; } };
Solution Breakdown:
Time Complexity: $O(N*C)$
The function knapSack
makes a lookupTable
within the function that stores the maximum value that can be attained with maximum capacity (lines 29-35). This function calls the helper function knapsackRecursive
(line 36). It returns the maximum value that can be attained using only the first i
items, i.e., items at the currentIndex
while keeping their total weight no more than weights.
We have two varying values (capacity
and currentIndex
), so we can use a two-dimensional array to store the results of all the solved subproblems in our recursive function knapsackRecursive
.
We need to store results for every subarray, i.e., for every possible index and for every possible capacity. If the lookupTable[currentIndex][capacity]
is already computed before (line 10), this value is immediately returned (line 11).
Otherwise, we call the function recursively:
With the item, saving the result in profit1
(line 17).
Without the item, saving the result in the variable, profit2
(line 21).
Out of the two, we return the result that is greater (as done on lines 23-24).
Problem Statement:
A child is running up a staircase with n
steps and can hop either 1 step, 2 steps, or 3 steps at a time. Implement a function to count the number of possible ways that the child can run up the stairs.
Try to solve this one using a Bottom-Up approach with Tabulation.
class StairCaseProblem { public static int countWays(int n) { // write your code here return -1; } };
Solution Breakdown:
Time Complexity: $O(n)$
We know that:
The total number of ways to reach the zero-step is 1 (line 6).
The total number of ways to reach the first step is 1 (line 7).
The total number of ways to reach the second step is 2 (line 8).
Hence, we fill up the lookupTable
with these three values (lines 6-8).
We know that the total number of ways to reach any n
th stair is by taking 1, 2, or 3 steps. Hence, the total number of ways to reach an n
th stair would be equal to the sum of the total number of ways to reach [n-1]
th step, number of ways to reach [n-2]
th step, and the number of ways to reach the [n-3]
th step.
So, the rest of the values of the lookupTable
are filled by calculating the total number of ways to reach an nth step by summing the ways to reach the previous three steps (line 11).
The required value is then returned from the lookupTable
(line 13).
Greedy is an algorithmic technique where the solution is built one piece at a time, prioritizing immediate, obvious benefits at each choice. In other words, it seeks to maximize profit (the positive) and minimizes the cost (the negative).
This technique works on the idea that the locally optimal choice will contribute to the globally optimal solution. Below we’ll see a few interview questions to help you use this technique when required.
Problem Statement:
You have to make such a change machine that only returns the change in the form of coins.
You are supplied with an infinite number of quarters (25 cents), dimes (10 cents), nickels (5 cents), and pennies (1 cent). The user will enter any amount. For each amount, you have to return the minimum number of coins possible!
class ChangeMachine { // a public collection of available coins public static int [] coins = {25, 10, 5, 1}; public static ArrayList<Integer> getMinCoins(int amount) // function to recieve change in the form of coins { ArrayList<Integer> change = new ArrayList<Integer>(); // your awesome code goes here return change; } }
Solution Breakdown:
Time Complexity: $O(n^2)$
Line 3: A public array is given containing the set of coins available.
Line 6: The function getMinCoins()
is defined; it has ArrayList as its return type and int amount as its parameter.
Line 9: The ArrayList of type Integer is allocated to store the change.
Lines 10-17: A for loop traverses the int[]coins
array from beginning to end (given in descending order).
Line 12: Since the first index of coins has the maximum element, compare in the while condition whether this amount is greater than the max coin.
Line 14: If yes, subtract the max value coin from the amount given.
Line 15: Add this coin to the change list.
Line 17: When the largest coin becomes greater than the remaining amount, the while loop breaks and the value of i
is incremented to move to the next (lesser value) coin.
Keep iterating this for loop, until the remaining amount can no longer be subdivided by the available coins.
Problem Statement:
Every positive fraction can be represented as the sum of its unique unit fractions. A fraction is a unit fraction if the numerator is 1 and the denominator is a positive integer. For example, $\frac 13$ is a unit fraction. Such a representation is called Egyptian fraction.
class Fraction { public static void printEgyptianFraction(int numerator, int denominator) { //write your code here int n = -1; //calculate the correct value System.out.print("1/" + n + " , "); //printing out your solution } }
Solution Breakdown:
Time Complexity: $O(log_3)$
For a given number of the form $\frac nd$, where d > n, first find the greatest possible unit fraction, and then perform recursion for the remaining part.
For example, consider $\frac 6{14}$. We first find the ceiling of $\frac {14}6$ , i.e., 3, so the first unit fraction becomes $\frac 13$. Now subtract $\frac 13$ out of $\frac 6{14}$ and recur for $\frac 6{14}$ – $\frac 13$.
We use the greedy algorithm because we want to reduce the fraction to a form where the denominator is greater than the numerator and the numerator doesn’t divide the denominator.
The method is to find the biggest unit fraction we can and subtract it from the remaining fraction. Doing subtractions always decreases this group of unit fractions, but it never repeats a fraction and eventually will stop, which is why we call this approach greedy.
Similar to Dynamic Programming, Divide and Conquer algorithms work by breaking down a problem into sub-problems. Where they differ is that Divide and Conquer algorithms solve each sub-problem then combine the results to form the ultimate solution whereas the sub-problems in Dynamic Programming are fully separate.
This is another staple type of algorithm that will be tested in your coding interview.
Problem Statement:
Given two integers a
and b
, calculate the largest number (GCD) that divides both of them without leaving a remainder.
class EuclideanAlgorithm { public static int GCD(int a, int b) { // your awesome code goes here! return -1; } }
Solution Breakdown:
Time Complexity: $O(log min(a,b))$
a
, which was obtained by b \%ab%a
in recursive calls) is 0.b
.GCD(b % a, a)
.Problem Statement:
Given an array of contiguous integers starting from x
, with one missing integer in between, and the size of the array, find the missing number!
class MissingNumber { public static int missingNumber(int arr[], int size) { // your awesome code here return Integer.MIN_VALUE; } }
Solution Breakdown:
Time Complexity: $O(log_n)$
Line 38: The driver program calls the function missingNumber()
with int [] arr
and int size
as its parameters.
Line 6: Initialize the right and left limits.
Lines 9-10: Handles corner case 1. Return 1
if array’s 1st element is not equal to 1.
Line 12-18: Begin by finding the middle index of the array, if the element at middle
is not equal to middle + 1
, and this is the first missing element, middle + 1
is the missing element.
Lines 21-26: If this is not the first missing element and arr[middle]
is not equal to middle+1
, search in the right half. Otherwise, search in the left half of the array.
Line 28: Handles corner case 2. Return -1
if you end up traversing the whole array and no element is missing.
Get all the practice and resources you’ll need to crack any Java algorithm-based interview question. Educative’s course come ready-built with an embedded coding environment, making learning fun and effective.
For our final section we’ll look at problems to build proficiency with common graph-related questions. These questions are becoming increasingly popular in interviews due to their prevalence in social-media mapping, meaning now more than ever it’s key to come prepared with this practice.
Problem Statement:
Implement a function that returns the number of nodes at a given level of an undirected graph.
class CountNodes { public static int numberOfNodesInGivenLevel(Graph g, int source, int level) { int count = 0; //count initialized to zero int num_of_vertices = g.getVertices(); //getVertices given in Graph.java file // Write - Your - Code return count; } }
Solution Breakdown:
Time Complexity: $O(V + E)$
The solution above modifies the visited array to store the level of each node. Later, it counts the nodes with the same level (lines 32-35).
In this code, while visiting each node, the level of the visited node is set with an increment in the level of its parent node, i.e.,
visited[child] = visited[parent] + 1
This is how the level of each node is determined (line 26).
Problem Statement:
Implement a function that takes a directed graph as input and print its transpose.
class Transpose { public static void getTranspose(Graph g) { int num_of_vertices = g.getVertices(); //getVertices defined in Graph.java file Graph transposedGraph = new Graph(num_of_vertices); //new graph to store the transpose //Write your code here transposedGraph.printGraph(); //calling print function on the final transposed graph } }
Solution Breakdown:
Time Complexity: $O(V + E)$
First, you make another graph and start reversing it. Traverse the adjacency list of the given graph. When the program finds a vertex v
in the adjacency list of vertex u
(i.e., an edge from u
to v
in the given graph), add an edge from v
to u
in the transposedGraph
, adding u
in the adjacency list of vertex v
of the new graph) (lines 9-13).
In line 19, the printGraph()
function prints the graph to console. You can find its implementation in Graph.java
file (lines 29-36).
n
pipes with the minimum costGreat work! Hopefully, you can already feel that pre-interview anxiety starting to melt away.
While this was a deep dive into 15 of the most common algorithm questions, there are many more possibilities that may come up in your interview. Varied practice is essential to success in a coding interview.
Be sure to keep studying, finding more interview resources, and most of all keep practicing other Java coding interview questions!
If you’re unsure where the road to your dream front-end dev job leads next, take a look at our free interview roadmap to help you get quickly.
Join a community of 500,000 monthly readers. A free, bi-monthly email with a roundup of Educative's top articles and coding tips.