Ace the top 15 Java algorithm questions for coding interviews

Ryan Thelin
Jul 29, 2020
editor-page-cover

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 onto 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.

Crack the coding interview with hands-on practice.

Dive into 50+ Java algorithm questions, each with hands-on practice and explanation.

Algorithms for Coding Interviews in Java


Measuring Complexity: Big O Notation

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.


1: Big O of a Nested Loop with Addition

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:

  • Initialization
  • Comparison
  • Incrementation

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 (n3+1)(\frac n3 + 1) times and the increment runs n3\frac n3 ​times.

Similarly, (int j = 0) runs n3\frac n3 times , the comparison (j < n) runs n3(n2+1)\frac n3 * (\frac n2 + 1), and the increment (j = j + 2) gets executed n2\frac n2 times for each iteration of the outer loop, so it runs:

n3n2=n26\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 we find our out Big O Complexity.


Sorting and Searching: Quicksort, Binary Search and more

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: Remember, 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.


2: Quicksort

Problem Statement:

Given an unsorted array of numbers, find Kth smallest number in it.

Please note that it is the Kth smallest number in the sorted order, not the Kth 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)O(N) or worst case O(N2)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:

  • If pivot’s position is larger than K-1, we recursively partition the array on numbers lower than the pivot.
  • If pivot’s position is smaller than K-1, we recursively partition the array on numbers greater than the pivot.

3: Binary Search

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)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).


4. Mergesort

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 sorted linked list.

class MergeSort{
  public static LinkedListNode mergeSort(LinkedListNode head) {
    //TODO: Write - Your - Code
    return head;
  }
}

Solution Breakdown:

Time Complexity: O(nlogn)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 n2\frac n2 and the total cost of combining step (merging sorted lists) is nn.


5. Insertion Sort

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(n2)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.


6. HashMap

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 which 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)O(n)

For all the elements in the arr array, we store the difference n - arr[i] in hmap.

Than with another iterration over arr, we check if any element of arr exists in the hmap , that 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 hash-map contains any array element, result[] is updated, or else it is returned containing the default value.


7. Hashset

Problem Statement:

Implement a 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)O(m+n)

First 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.


Keep the learning going.

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.

Algorithms for Coding Interviews in Java



Dynamic Programming: Memoization and Tabulation

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.

8. The Knapsack Problem:

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 gives 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(NC)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).


9. Staircase Problem

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)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 nth stair is by taking 1, 2, or 3 steps. Hence, the total number of ways to reach an nth 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 Algorithms: Local Maximization

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, or the positive, and minimizes the cost, or 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.


10: Change Machine Problem

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(n2)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.


11: Find the Egyptian Fraction

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, 13\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(log3)O(log_3)

For a given number of the form nd\frac nd, where d > n, first find the greatest possible unit fraction, and then perform recursion for the remaining part.

For example, consider 614\frac 6{14}. We first find the ceiling of 146\frac {14}6 , i.e., 3, so the first unit fraction becomes 13\frac 13. Now subtract 13\frac 13 out of 614\frac 6{14} and recur for 614\frac 6{14}13\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.


Divide and Conquer:

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.

12: Euclidean Algorithm Problem

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(logmin(a,b))O(log min(a,b))

  • Line 5: The algorithm starts by checking if the first number (a , which was obtained by b \%ab%a in recursive calls) is 0.
  • Line 6: If that is the case, then return b.
  • Line 7: Otherwise, we make the next recursive call GCD(b % a, a).

13: Missing number in Sorted Array

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(logn)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.


Graphs Algorithms:

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.

14: Calculate the Number of Nodes in a Given Graph Level

Problem Statement:

Implement a function that returns the number of nodes at a given level of an undirected graph.

main.java
Graph.java
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)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).


15: Transpose a Graph

Problem Statement:

Implement a function that takes a directed graph as input and print its transpose.

main.java
Graph.java
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)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).


More coding interview questions to ace algorithms:

  1. Search in a rotated array
  2. Find the median of two sorted arrays
  3. Find duplicates in an array
  4. The Dutch National Flag Problem
  5. Find the longest common substring in a string
  6. The Egg Drop Problem
  7. Find the longest palindromic subsequence of a string
  8. The Edit Distance Problem
  9. Connect n pipes with the minimum cost
  10. The Trainstation Platform Problem
  11. The Fractional Knapsack Problem
  12. Find Kruskal’s minimum spanning tree
  13. Find the peak element in an array
  14. Shuffle the integers of an array
  15. Search a graph breadth-first
  16. Search a graph depth-first
  1. Count the paths between two nodes
  2. Print all connected components in a graph
  3. Remove an edge of a graph
  4. Implement topological sorting of a graph
  5. Check if a graph is strongly connected
  6. Check if a graph is Bipartite
  7. Find the floor and ceiling of a number
  8. Find the closest number in an array
  9. Collect coins in the least steps
  10. Find the maximum sum of two subarrays
  11. The Coin Change Problem
  12. The Partition Problem
  13. Count element occurrence
  14. The Sparse Search Problem

Where to go from here

Great 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!


Interview Roadmap

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.


Keep Reading

WRITTEN BY

Ryan Thelin


A free, bi-monthly email with a roundup of Educative's top articles and coding tips.