Thursday, June 11, 2020

Dynamic Programming

Problems having optimal substructure and overlapping subproblems can be solved by dynamic programming, in which subproblem solutions are Memoized rather than computed again and again. The problems are usually solved in bottom-up manner, were smaller sub-problems are solved first, then the larger sub-problems are solved from them.

Memoization Matrix usually has the column with increasing number from 0 to final result, while the row has the elements which needs to selected for the optimal solution. The first column (and sometimes row) has the 0th column which is a hypothetical condition providing initial values with the matrix.

Levenshtein Distance

Determine the minimum edit distance for converting one string to another string

The Levenshtein distance is a string metric for measuring the difference between two sequences. It is the minimum number of single-character edits (insertions, deletions or substitutions) required to change one word into the other. Here we first create a matrix with Source String as the columns while Destination String as rows. If the row and column character are same then no operation is needed and we directly copy the previous value of the diagonal cell as it is. If the row and column character is different then we find the minimum previous value from the top, side and diagonal, add 1 to it to get the final value as below.
Character Operations: Replace, Delete, Insert.





Below is the code for Levenshtein Distance.
static void editDP(String s1, String s2) 
    { 
        int l1 = s1.length(); 
        int l2 = s2.length(); 
        int[][] DP = new int[l1 + 1][l2 + 1]; 
  
        // initilize by the maximum edits possible 
        for (int i = 0; i <= l1; i++) 
            DP[i][0] = i; 
        for (int j = 0; j <= l2; j++) 
            DP[0][j] = j; 
  
        // Compute the DP matrix 
        for (int i = 1; i <= l1; i++) { 
            for (int j = 1; j <= l2; j++) { 
  
                // if the characters are same 
                // no changes required 
                if (s1.charAt(i - 1) == s2.charAt(j - 1)) 
                    DP[i][j] = DP[i - 1][j - 1]; 
                else { 
  
                    // minimu of three operations possible 
                    DP[i][j] = min(DP[i - 1][j - 1], 
                                   DP[i - 1][j], DP[i][j - 1]) 
                               + 1; 
                } 
            } 
        }  
    } 



Coin Change Problem

When given coins of different denominations and a total amount, find out the number of combinations that make up that amount. Here the assumption is that there are unlimited supply of given coin denominations.
In the below table shows the P(n) for each number, which is the Number of ways representing an Integer as addition of positive Integers.

n Ways P(n)
1 (1) 1
2 (2), (1+1) 2
3 (3), (2+1), (1+1+1) 3
4 (4), (3+1), (2+2), (2+1+1), (1+1+1+1) 5


Lets see below example

Amount = 12
Coins = {1, 2, 5}

ForEach Coin in the given coin denominations i.e. {1, 2, 5}

If Amount >= Coin then
   Combinations[Amount] += Combinations[Amount - Coin]


First initialize the Combinations array with 0 value for all elements.

Combinations when Coin is 1
Set Combinations[0] = 1 when coin is 0, which means using coin 0 to achieve amount 0 there is only 1 combination.

Then we use the above logic to determine the next value of the combination when coin is 1 and amount is 1.
Combinations[1] = Combinations[1-1] + Combinations[1] = Combinations[0] + Combinations[1] = 1 + 0 = 1

Similarly, 
Combinations[2] = Combinations[2-1] + Combinations[2] = Combinations[1] + Combinations[2] = 1 + 0 = 1


0 1 2 3 4 5 6 7 8 9 10 11 12
1 1 1 1 1 1 1 1 1 1 1 1 1


Combinations when Coin is 2
When Amount is 0 or 1 and Coin is 2, the condition (Amount >= Coin) becomes false, so we skip for Amounts 0 and 1.

When Amount is 2 and Coin is 2, we have,
Combinations[2] = Combinations[2-2] + Combinations[2] = Combinations[0] + Combinations[2] = 1 + 1 = 2

The original value of Combinations[2] was 1 from above.
Similarly when Amount is 3 and Coin is 2, we have,
Combinations[3] = Combinations[3-2] + Combinations[3] = Combinations[1] + Combinations[3] = 1 + 1 = 2

Similarly when Amount is 4 and Coin is 2, we have,
Combinations[4] = Combinations[4-2] + Combinations[4] = Combinations[2] + Combinations[4] = 2 + 1 = 3


0 1 2 3 4 5 6 7 8 9 10 11 12
1 1 2 2 3 3 4 4 5 5 6 6 7


Combinations when Coin is 5
When Amount is 0 to 4 and Coin is 5, the condition (Amount >= Coin) becomes false, so we skip Amounts 0 and 4.

When Amount is 5 and Coin is 5, we have,
Combinations[5] = Combinations[5-5] + Combinations[5] = Combinations[0] + Combinations[5] = 1 + 3 = 4

Similarly when Amount is 6 and Coin is 5, we have,
Combinations[6] = Combinations[6-5] + Combinations[6] = Combinations[1] + Combinations[6] = 1 + 4 = 5


0 1 2 3 4 5 6 7 8 9 10 11 12
1 1 2 2 3 4 5 6 7 8 10 11 13


Another example were Amount = 4 and Coins = {1, 2, 3}


Below is the code for Coin Change Problem

int change(int amount, int[] coins) {

  int[] combinations = new int[amount + 1];
  combinations[0] = 1;

  for(int coin : coins) {
    for(int i = 1; i < combinations.length; i++) {
       if(i >= coin) {
         combinations[i] += combinations[i - coin];
       }
    }
  }
}



Longest Common Subsequence (LCS) Problem

Longest Common Subsequence Problem is to find the common subsequences but are not required to occupy consecutive positions with the original sequences.
For example, consider the following sequences, A and B

A = bqdrcvefgh
B = abcvdefgh

Here the length of LCS is 7,
LCS are bdefgh and bcvefgh

We first construct a matrix with columns as characters of string A and rows as characters of string B.
  • If one of the string is empty or both the strings are empty then the length of the longest common subsequence is 0, hence we put zero for both the empty character row and empty character column.
  • When the row character matches with the column character for the cell, then take the previous diagonal value in the matrix and add 1 since there is a match, to get the final value of the cell.
  • When the row character does not match with the column character for the cell, then take the maximum between the above cell and the left cell, to get the final value of the cell.



int longestCommonSubsequence(String A, String B) {

  int m = A.length(), n = B.length();

  int[][] table = new int[m+1][n+1];

  for(int i = 0; i <= m; i++) {
     table[i][0] = 0;
  }

  for(int j = 0; j <= n; j++) {
     table[0][j] = 0;
  }

  for(int i = 1; i <= m; i++) {

     for(int j = 1; j <= n; j++) {

        if(A.charAt(i - 1) == B.charAt(j - 1))
           table[i][j] = table[i - 1][j - 1] + 1;
        else 
           table[i][j] = Math.max(table[i - 1][j], table[i][j - 1]);
     }
  }

  return table[m][n];
}


Subset Sum Problem

Given a set of positive integers and an integer s, find if there is any non-empty subset whose sum is s.

For example set = { 1, 2, 7, 5 }
sum = 8 has subset { 1, 2, 5 }
sum = 4 has no subset

First the given set is sorted in ascending order.

Logic:
Since this is a dynamic programming problem we create a matrix with columns as 0 incrementing by 1 till the sum s. The rows are each element in the example set in a sorted order.

Truth Value = (Truth Value by Excluding New Value) OR (Truth Value by Including New Value)

Truth value by excluding the new value in the current cell comes from the value of upper cell within the matrix.
Truth value by including the new value in the current cell comes from getting the cell value of previous/upper row and the column equal to (sum - new value of the row) within the matrix.
Finally, we return true if we get subset by including or excluding current item else we return false.

To find the subset which gives the sum s, we go to the last cell and backtrack to check were the True value is coming from. If value in the cell above it is False, then it means current cell has become True after including the current element. So include the current element and check for the sum = sum – current element.


public static boolean subSetDP(int[] A, int sum) {
        boolean[][] solution = new boolean[A.length + 1][sum + 1];
        // if sum is not zero and subset is 0, we can't make it 
        for(int i=1; i<=sum; i++){
            solution[0][i]=false;
        }
        // if sum is 0 the we can make the empty subset to make sum 0
        for(int i=0; i<=A.length; i++){
            solution[i][0]=true;
        }
        
        // do for ith item
        for(int i=1; i<=A.length; i++){
            // consider all sum from 1 to sum
            for(int j=1; j<=sum; j++){
            
                // don't include ith element if j-array[i-1] is negative
                if(A[i-1] > j) {
                   //first copy the data from the top
                   solution[i][j] = solution[i-1][j];
                }
                else {
                    // find subset with sum j by excluding or including the ith item
                    solution[i][j] = solution[i-1][j] || solution[i-1][j - A[i-1]];
                }
            }
        }
        return solution[A.length][sum];
    }


Partition Problem

Given a set of positive integers, find if it can be divided into two subsets with equal sum.

Example Set S = {3,1,1,2,2,1}
We can partition S into two partitions each having sum 5.
S1 = {1,1,1,2}
S2 = {2,3}

Another solution could be,
S1 = {3,1,1}
S2 = {2,2,1}

// Return true if there exists a subset of array[0..n) with given sum
private bool subsetSum(int arr[], int n, int sum)
{
	// T[i][j] stores true if subset with sum j can be attained with
	// using items up to first i items
	bool T[n + 1][sum + 1];

	// if 0 items in the list and sum is non-zero
	for (int j = 1; j <= sum; j++)
		T[0][j] = false;

	// if sum is zero
	for (int i = 0; i <= n; i++)
		T[i][0] = true;

	// do for ith item
	for (int i = 1; i <= n; i++)
	{
		// consider all sum from 1 to sum
		for (int j = 1; j <= sum; j++)
		{
			// don't include ith element if j-arr[i-1] is negative
			if (arr[i - 1] > j)
				T[i][j] = T[i - 1][j];
			else
			// find subset with sum j by excluding or including the ith item
				T[i][j] = T[i - 1][j] || T[i - 1][j - arr[i - 1]];
		}
	}

	// return maximum value
	return T[n][sum];
}

// Partition problem - Return true if given array arr[0..n-1] can
// be divided into two subsets with equal sum
public bool partition(int arr[], int n)
{
	int sum = 0;
	for (int i = 0; i < n; i++)
	sum += arr[i];

	// return true if sum is even and array can can be divided into
	// two subsets with equal sum
	return !(sum & 1) && subsetSum(arr, n, sum/2);
}



0/1 Knapsack Problem

We are given a set of items, each with a weight and a value (or profit) and need to determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit and the total value is as large as possible. The 0/1 indicates that the item can either be selected or not selected, i.e. items are indivisible.

Sum of the Weights <= Total Weights
Sum of the value should be maximum

Consider the below example were we have items {a, b, c, d, e} with weights and values as below. We need to in the subset with maximum sum of value were sum of their weights should be equal to or less then 9. The quantity of all the items is 1. We create the matrix as below, with the columns as increasing number of weights aka Sum of weights from 0 to the total weight. The rows of the matrix are the sorted using item values.


weight value
a 1 1
b 3 4
c 4 5
d 5 7
e 2 4
item value weight 0 1 2 3 4 5 6 7 8 9
a 1 1 0 1 1 1 1 1 1 1 1 1
e 4 2 0 1 4 5 5 5 5 5 5 5
b 4 3 0 1 4 5 5 8 9 9 9 9
c 5 4 0 1 4 5 5 8 9 10 10 13
d 7 5 0 1 4 5 5 8 9 11 12 13

We cannot achieve the zero weight, using the weights of any items so entire column is assigned the value 0.  For column with weight 1, since item a which has weight 1 can contribute in forming the sum of the weights from 1 to 9, we add 1 to entire row. Now we follow below logic to determine the current value of the cell,

1)   If item weight <= sum of weights,
      value = maximum of(value by excluding the new item, value by including the new item)

To calculate the value by including the new item, we add
  • value of the new item (not the weight)
  • value from above row and the column (sum of weights in column - current item's weight).
2)   If item weight > sum of the weights, copy the value from the above cell.

Use backtracking to determine from were the final value of 13 came to determine the weights or the items included in the result collection.

private int knapSack(int sumWeight, int weights[], int values[]) { 

        int n = values.length;
        
        // K[i][j] stores the maximum value that can be attained with
        // weight less than or equal to j using items up to first i items
        int K[][] = new int[n + 1][sumWeight + 1]; 

	    for (int j = 0; j <= sumWeight; j++) {
		   K[0][j] = 0;
        }

        for (int i = 1; i<= n; i++) { 
        
            // consider all weights from 0 to maximum capacity sumWeight
            for (int j = 0; j<= sumWeight; j++) { 
                    
               	// don't include ith element if j-weights[i-1] is negative
			    if (weights[i-1] > j)
				    K[i][j] = K[i-1][j];
			    else
			    // find max value by excluding or including the ith item
				    K[i][j] = Math.max(K[i-1][j], K[i-1][j-weights[i-1]] + values[i-1]);
            } 
        } 
  
        return K[n][sumWeight]; 
    } 


Rod Cutting Problem

Given a rod of length n and list of pieces of rod of length i where 1 <= i <= n, find the optimal way to cut the rod into smaller rods in order to maximize the profit.

Rod of length = 5
lengths of pieces = {1, 2, 3, 4}
profits of pieces = {2, 5, 7, 8}

If we cut the rod in the given lengths of piece, then profit of each piece is given in corresponding profits set. The rod has to be cut in such a way that we get the maximum profit.


We create the memoization matrix, with column as total length of from 0 to length of the rod, and rows as length of each piece given in the set. Each cell has the profit value for the corresponding piece with the length.

The piece with length 0 is hypothetical condition, hence, entire row's values is 0.
Also if we want to make the total length of 0 for column with sum of length as 0, none of the piece from length 1 to 4 satisfy the condition, hence all entries there are 0.
If we have a piece of length 1 and we want total length to be 1, we need only 1 piece with length 1 to achieve that, hence the profit value of piece 1 is added to the corresponding cell. Similarly, to achieve the total length of 2, we need two pieces of length 1, hence we add profit value of piece 1 twice to corresponding cell. Similar goes for columns 3,4 and 5 with row of piece length 1.

When the row piece length is greater than column total length we copy the cell value from upper row. If the row piece length is less than or equal to column total length, then we use the below logic. Profit by including new piece is,

(Profit of current piece) + 
(Profit of cell with column as (Current Total length - Current piece length) in current row).

Profit = max (Profit by excluding new piece, Profit by including new piece)

profit length 0 1 2 3 4 5
0 0 0 0 0 0 0 0
2 1 0 2 4 6 8 10
5 2 0 2 5 7 10 12
9 3 0 2 5 9 11 14
6 4 0 2 5 9 11 14

// n is the maximum length of the rod
public int rodCut(int[] price, int n)
	{
		// T[i] stores maximum profit achieved from rod of length i
		int[] T = new int[n + 1];
        T[0] = 0;

		// consider rod of length i
		for (int i = 1; i <= n; i++)
		{
			// divide the rod of length i into two rods of length j
			// and i-j each and take maximum
			for (int j = 0; j < i; j++) {
				T[i] = Math.max(T[i], price[j] + T[i - (j + 1)]);
			}
		}

		// T[n] stores maximum profit achieved from rod of length n
		return T[n];
	}



Word Break Problem

Given a string and a dictionary of words, determine if string can be segmented or split into a space-separated sequence of one or more dictionary words.

E.g. dictionary[] = { "this", "th", "is", "famous", "break", "b",
                    "r", "e", "a", "k", "br", "bre", "brea", "ak", "problem"}
string = "Wordbreakproblem"





Saturday, June 6, 2020

Programming Interview - Mathematics Basics


When the order doesn't matter, it is a Combination. When the order does matter it is a Permutation.

There are 2 types of Permutation, repetition is allowed and no repetition allowed.

Permutations with Repetition = n ^ pow(r) = n * n * n * ... (r times)
Here n is the number of things to choose from, and we choose r of them, repetition is allowed, and order matters.

Permutations without Repetition = n! / (n − r)!
Here n is the number of things to choose from, and we choose r of them, no repetitions and order matters.

Similarly there are 2 types of Combination, repetition is allowed and no repetition allowed.

Combinations without Repetition = n! / ( r! * (n − r)! )

Combinations with Repetition = (r + n - 1)! / ( r! * (n − 1)! )


REMEMBER: (a + b) / 2 = a + (b - a) / 2    (Used as (a+b) could overflow Integer's range)

Thursday, May 28, 2020

Programming Interview - Mathematics Problems

Find all factors of the number ?

A number is always divisible by 1 and itself. The smallest factor a number can have is 2, while largest factor would be n/2. But as a * b = number n, were a and b are co-efficient, the pairs of numbers will be duplicated after a = b, i.e. square-root of n. Hence we only need to loop till square-root of n, and add both i and n/i to the list of factors.

Verify if number is a prime number ?

To verify a prime number we only need to loop through from 2 to square-root of number n. As the number n to be non-prime there must be factors "a" and "b" such that a = n / b, i.e. a * b = n. As we observe for example number 36, the factor pairs {1,36} {2,18} {3,12} {4,9} {6,6} {9,4} {12,3} {18,2} {36,1} are repeated after {6,6} which is after square-root of 36, i.e. 6. Hence after square-root of n, the numbers would be either repeated factor-pairs, multiple of other prime numbers.

Also 1 is not regarded as a prime number. 

Fibonacci Sequence

F(n) = { F(n - 1) + F(n - 2)   if n > 1
           {  n                             if n = 0,1 }

0  1  1  2  3  5  8  ...
The first two terms of the series are 0, 1.

Using Recursion: if(n <= 1)  return n;   return fibonacci(n-1) + fibonacci(n-2)

Recursion: Exponential Time
Fibonacci Sequence Recursion: O(2^n)

Iteration: Here we need three variables, the previousNumber, the currentNumber and a temp Number as we cannot assign the new value to currentNumber directly because, we have to first move currentNumber's value to previousNumber.
    System.out.print(previousNumber + ", ");

    for (int i = 1; i < n; i++) {

        newNumber = currentNumber + previousNumber;
        previousNumber = currentNumber;
        currentNumber = newNumber;

        System.out.print(currentNumber + ", ");
    }
  
Modular Exponentiation

Calculate: x^n mod M
Since integer is 32 bit, with 1 bit reserved for sign, the range of integer is -2^31 to 2^31, hence calculating x^n directly will not fit many cases within integer range.

Since (a * b) % M = {(a % M) * (b % M)} % M
x^n % M = (x * x^n-1) % M
        = {(x % M) * (x^n-1 % M)} % M

When n is even,  {(x^n/2 % M) * (x^n/2 % M)} % M
Time complexity is O(log n)

Power(x,n) i.e. x^n
x^n = { x^n/2 * x^n/2  if n is even
        x * x^n-1      if n is odd
        1              if n=0       }

To calculate the time complexity, use the variance of the number being updated and passed to the recursive function.
T(n) = T(n/2) + C
T(n) = T(n/2^k) + K*C    assuming this is done k times.
Hence k is log2(n) which is the time complexity.

Find all factors of the number:

12 = {1,2,3,4,6,12}
Trial Division were we try to divide the number with all the numbers from 1 to n itself.
But 1 and the number itself are its factors. Also since the smallest factor of the number is 2 and largest factor is n/2, there are no factors of the number above n/2. Also if a * b = n, assuming a.b are factors, then when a > sqrt n, then b < sqrt n. Further when a = sqrt n, then a = b = sqrt n. Hence only search factor till square root of number, as remaining are just number/factor.
Divide all the numbers starting from 2 to square-root of n, and add [current-no] with (n / [current-no]) to the factors list. Numbers 1 and n are already added to factors list.

Time complexity is O(sqrt n)


24 = 3 * 2 * 2 * 2

Start dividing the number n by smallest number (starting with 2) such that the remainder is 0. Continue dividing the quotient from previous division until the remainder is non-zero, then select the next number for division of the quotient. The loop continues selecting next smallest number (i) until the (i) becomes greater than square-root of quotient, which indicates that the quotient is a prime number. Since we already know that there cannot be any factors for a number (quotient here) after square-root of that number.

Time complexity is O(sqrt n)

How do implement Sieve of Eratosthenes Algorithms for Prime Number?
How to check if a given year is a leap year in Java?
How to convert a decimal number to binary in Java?
How to reverse given Integer in Java?
How to find the largest prime factor of a given integral number?
How to print Floyd’s triangle?
How to print Pascal’s triangle? (solution)
How to calculate the square root of a given number?


Programming Interview - Core

  1. Sorted array is pre-condition for binary Search. Take two pinter with start = 0, end = n-1 where n is length of the array.
  2. Search a sorted array by repeatedly dividing the search interval in half. 
  3. mid = (start + end) / 2
  4. Case 1:  x == A[mid]   return mid
  5. Case 2:  x < A[mid]    end = mid - 1    (x lies before the mid element, so shift end)
  6. Case 3:  x > A[mid]    start = mid + 1    (x lies after the mid element, so shift start)
  7. Continue till start <= end, (because even when start == end, we have 1 element to check)
If the value of the search key is less than the item in the middle of the interval, narrow the interval to the lower half. Otherwise narrow it to the upper half. Repeatedly check until the value is found or the interval is empty.
  int binarySearch(int array[], int start, int end, int numberToFind);
  
Complexity: (n / 2^k) = 1, were k is no of steps to reach result, while n is length of array. This gives k = log n. Hence the time complexity is O(log n).

Recursive Version:
  • binarySearch(array, start, end, numberToFind)
  • When start > end, then return -1
  • mid = (start + end) / 2
  • Case 1: x == array[mid], then return mid
  • Case 2: x < array[mid],   then call binarySearch(array, start, mid-1, numberToFind)
  • Case 3: x > array[mid],   then call binarySearch(array, mid+1, end, numberToFind)

Binary Search - First Occurrence of the element

We have a sorted array with duplicate elements to find the first occurrence of the element.

Modify the Case1 as below:

result = -1  (initialize the result with -1 indicating that no match found)
Case 1:  x == A[mid]   then  
              result = mid;     (capture the result i.e. the position of the matched element) 
              end = mid - 1    (to find first occurrence of the element x, check if there is one more occurrence of x before matched mid element, hence we shift end before mid)

Finally return result

Binary Search - Last Occurrence of the element

Similar to above we modify the Case1 as below:

Case 1:  x == A[mid]   then  
              result = mid;     (capture the result i.e. the position of the matched element) 
              start = mid + 1    (to find last occurrence of the element x, check if there is one more occurrence of x after the matched mid element, hence we shift start after mid)

Finally return result
Time complexity of both these variations of binary search is O(log n).

Count number of occurrences of element in sorted array

Strategy 1: Loop through the sorted array until there is a match and count until there is no match.
Strategy 2: Find the element in the sorted array using binary search, then first shift left and then shift right to count all the occurrences of the element.
Strategy 3: Find the first occurrence of element using binary search and then the last occurrence of element using binary search, using that determine the count of occurrences since the array is sorted i.e. (Last - First + 1).

Search element in a circular sorted array

Given a sorted circular array were the start element followed by end element lies somewhere in the middle of the array. 
E.g. 12,  14,  18,  21,  3,  6,  8,  9
      low               mid                high

The start element in the above array is 3. 
In the below code, x is the input element to be searched and we need to determine the position of x in the array.

mid = (low + high) / 2

Case1: A[mid] == x, return mid

Case2: A[mid] <= A[high]         // Right half is sorted

2A:    x > A[mid] && x <= A[high]
        low = mid + 1                   // search in right sorted half
Else
       high = mid - 1                    // search in left half

Case 3: A[low] <= A[mid]         // Left half is sorted

3A: x >= A[low] && x < A[mid]
       high = mid - 1                    // search in left sorted half
Else
        low = mid + 1                   // search in right half


Precedence of Operators



(high + low) / 2 is same as low + ((high - low)/2), as low + high can overflow if range exceeds integer.

Memoization
Memoization is an optimization technique used to speed up programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

Sorting



Quick Sort

It is in place sorting.
Average case: O(n log n)
Worst case: n pow 2

1) Select a Pivot.
2) Move the elements lower than pivot to left side, while move elements higher than pivot to right side.

In below example we select the pivot always as the last element.

QuickSort(A, start, end) { 
  if (start < end) {
    pIndex = Partition(A, start, end)
    QuickSort(A, start, pIndex-1)
    QuickSort(A, pIndex+1, end)
  }
}

Partition(A, start, end) {
 pivot = A[end]             // Pivot is always the last element of list
 pIndex = start
 for i=start to end-1 {
   if(A[i] <= pivot) {
     swap(A[i], A[pIndex])
     pIndex = pIndex + 1
   }
 }
 swap(A[pIndex], A[end])
 return
}

Merge Sort

1) Split the list into half, at the mid point. Continue to split the 2 lists each recursively further until only 1 element is present. 
2) Now merge all the splitted list earlier recursively from bottom up such that, the final list at each step is sorted. Note here while merging two lists, we first take original list which was splitted in those two lists in the first place. Loop through both the Left and Right splitter list to check which is greater/less and place them elements in sorted fashion into the original list. Repeat this for all list in each recursive step.

Merge(Left, Right, A) {
  sizeLeft = length(Left)
  sizeRight = length(Right)
  i = j = k = 0

  while(i < sizeLeft && j < sizeRight) {

    if( Left[i] <= Right[j] )  {
       
       A[k] = Left[i]
       k = k + 1
       i = i + 1
    }
    else {

       A[k] = Right[j]
       k = k + 1
       j = j + 1
    }
  }

  // If there are elements remaining in either Left or Right list then simply append to result list

  while(i < sizeLeft) {
     A[k] = Left[i]
     i = i + 1
     k = k + 1
  }
  while(j < sizeRight) {
     A[k] = Right[j]
     j = j + 1
     k = k + 1
  }
}

MergeSort(A) {

  n = length(A)
  if(n < 2) return
  mid = n/2
  left = array of size(mid)
  right = array of size(n - mid)
  for i = 0 to mid - 1
     left[i] = A[i]
  for i = mid to n - 1
     right[i-mid] = A[i]

  MergeSort(left)
  MergeSort(right)
  Merge(left, right, A)
}