Skip to content

Coding Test

In the coding test The three main techniques to solve programming problems are Divide and Conquer, Recursion, and Dynamic Programming. Divide and Conquer involves breaking down a problem into smaller sub-problems, solving each sub-problem independently, and then combining the solutions to solve the original problem. Examples include Merge Sort and Binary Search. Recursion is a method where the solution to a problem depends on solutions to smaller instances of the same problem, such as calculating the factorial of a number or solving the Tower of Hanoi problem. Dynamic Programming solves problems by breaking it down into simpler, smaller subproblems and storing the results of each subproblem to avoid duplicate work, as seen in the Fibonacci series and the 0/1 Knapsack problem. These techniques often involve trade-offs in terms of computation time and memory usage.

Simple Questions

In order to get a feeling of the expected difficulty, below are some examples of questions. It is expected that someone will be able to solve them very fast and without mistakes.

Question: Write a function in Python to check if a number is prime or not.

   def is_prime(n):
       if n <= 1:
           return False
       for i in range(2, n):
           if n % i == 0:
               return False
       return True

Question: Write a function to reverse a string.

Python

def reverse_string(str):
    return str[::-1]

C

#include <stdio.h>
#include <string.h>

void reverse_string(char* str) {
    int len, i;
    char temp;
    len = strlen(str);
    for (i = 0; i < len/2; i++) {
        temp = str[i];
        str[i] = str[len-i-1];
        str[len-i-1] = temp;
    }
}

Question: Write a function in Java to find the factorial of a number.

   static int factorial(int n) {
     if (n == 0)
         return 1;
     else
         return(n * factorial(n-1));
   }

Question: Write a function to check if a string is a palindrome.

A palindrome is a word, phrase, number, or other sequence of characters that reads the same forward and backward, ignoring spaces, punctuation, and capitalization.

For examples the word "racecar" is a palindrome. It reads the same way forward and backward. If you start reading from the first letter 'r' to the last 'r', it spells 'racecar'. Similarly, if you start reading from the last letter 'r' to the first 'r', it also spells 'racecar'. Therefore, 'racecar' is a palindrome.

Python

   def is_palindrome(s):
       for i in range(0,len(s)//2):
           if s[i] != s[-i-1]:
               return False
       return True

C++

   #include <bits/stdc++.h>
   using namespace std;
   bool is_palindrome(string str) {
       int start = 0;
       int end = str.size() - 1;
       while (start < end) {
           if (str[start] != str[end])
               return false;
           start++;
           end--;
       }
       return true;
   }
   int main() {
       string str = "madam";
       cout << (is_palindrome(str) ? "Yes" : "No");
       return 0;
   }

  1. Question: Write a function to find the sum of all elements in an array.
def sum_array(arr):
    sum = 0
    for a in arr:
        sum += a
    return sum

These are just some of the types of problems put in front during the screening part of the coding interview process.

Advanced

Depending on the organization a challenge problem can become more involved in terms of Efficiency of the requested solution, both in reducing computation time and memory usage. Usually, these are associated only with efficiently coding and applying software engineering techniques of well known algorithms. Below are some examples to understand the expected difficulty of this level.

There are three most important techniques that can be used to solve any problem. These are Divide and Conquer, Recursion, and Dynamic Programming. Let's got at them one by one and present some examples.


Divide and Conquer

Start by breaking down a problem into smaller sub-problems, solving each sub-problem independently, and then combining the solutions to solve the original problem.

Example: Merge Sort

Merge Sort is a classic example of divide and conquer. It divides the array into two halves, sorts them separately, and then merges them.

   def merge_sort(arr):
       if len(arr) > 1:
           mid = len(arr)//2
           L = arr[:mid]
           R = arr[mid:]
           merge_sort(L)
           merge_sort(R)
           i = j = k = 0
           while i < len(L) and j < len(R):
               if L[i] < R[j]:
                   arr[k] = L[i]
                   i += 1
               else:
                   arr[k] = R[j]
                   j += 1
               k += 1
           while i < len(L):
               arr[k] = L[i]
               i += 1
               k += 1
           while j < len(R):
               arr[k] = R[j]
               j += 1
               k += 1

The time complexity of Merge Sort is O(n log n) in all cases. However, it requires O(n) extra space for the temporary arrays L and R. Which means that for an input size array with n elements and time taken t, the time to merge an array of e.g. double the size (i.e. 2n) will be 2t, and so on.

Binary Search is another example of divide and conquer. It halves the search space at every step until the target is found.

   def binary_search(arr, l, r, x):
       if r >= l:
           mid = l + (r - l) // 2
           if arr[mid] == x:
               return mid
           elif arr[mid] > x:
               return binary_search(arr, l, mid-1, x)
           else:
               return binary_search(arr, mid + 1, r, x)
       else:
           return -1
The time complexity of Binary Search is O(log n). It doesn't require any extra space as it works in place, making it memory efficient. The code is a recursion, which is presented next, and can be easily turned into a loop at the expense of larger memory usage.


Recursion

Recursion is a method where the solution to a problem depends on solutions to smaller instances of the same problem.

Example: Factorial of a number

The factorial of a number can be calculated using recursion as n! = n*(n-1)!, where 0! = 1.

   def factorial(n):
       return 1 if (n==1 or n==0) else n * factorial(n - 1);

The time complexity is O(n) because for every number a function call is needed. The memory required depends on the language implementations of function call returns and the optimization for recursion. In programming languages like C++, the memory required can be constant.

Below is a tail recursive implementation of the factorial function in C++:

#include<iostream>
using namespace std;

int factorial_helper(int n, int result = 1) {
    if (n == 0 || n == 1)
        return result;
    else
        return factorial_helper(n - 1, n * result);
}

int factorial(int n) {
    return factorial_helper(n);
}

int main() {
    int num;
    cout << "Enter a positive integer: ";
    cin >> num;
    cout << "Factorial of " << num << " = " << factorial(num);
    return 0;
}

In this code, factorialHelper is a helper function that does the actual tail recursion. It takes an additional argument result which accumulates the result of the factorial calculation. The factorial function is a wrapper function that calls factorialHelper with the initial result of 1.

Example: Tower of Hanoi

The Tower of Hanoi is a classic problem that can be solved using recursion.

   def tower_of_hanoi(n , source, destination, auxiliary):
       if n==1:
           print("Move disk 1 from source",source,"to destination",destination)
           return
       TowerOfHanoi(n-1, source, auxiliary, destination)
       print("Move disk",n,"from source",source,"to destination",destination)
       TowerOfHanoi(n-1, auxiliary, destination, source)
The time complexity of the Tower of Hanoi is O(2^n). It uses O(n) space on the call stack.


Dynamic Programming

Dynamic Programming solves problems by breaking it down into simpler, smaller subproblems and storing the results of each subproblem to avoid duplicate work. It is efficiently implemented by holding a structure in memory (typically a two dimensional array) that stores all the previous sub-problems solutions before computing the next.

Example: Fibonacci Series

The Fibonacci series is a classic example where Dynamic Programming can be used. Here, we store the Fibonacci numbers calculated so far.

   def fibonacci(n):
       fib = [0, 1] + [0]*(n-1)
       for i in range(2, n+1):
           fib[i] = fib[i-1] + fib[i-2]
       return fib[n]
The time complexity is O(n), and the space complexity is also O(n) as we store all Fibonacci numbers till n.

Example: 0/1 Knapsack Problem

The 0/1 Knapsack problem is a classic problem that can be solved using Dynamic Programming. Here, we build a table K[][] in bottom-up manner.

   def knapsack(W, wt, val, n):
       K = [[0 for w in range(W+1)] for i in range(n+1)]
       for i in range(n+1):
           for w in range(W+1):
               if i == 0 or w == 0:
                   K[i][w] = 0
               elif wt[i-1] <= w:
                   K[i][w] = max(val[i-1] + K[i-1][w-wt[i-1]],  K[i-1][w])
               else:
                   K[i][w] = K[i-1][w]
       return K[n][W]
The time and space complexity of the 0/1 Knapsack problem is O(nW), where n is the number of items and W is the capacity of the knapsack.