Home/Coding Guide/Sorting, Searching & Hash Tables — Advanced
⌂ Main Page
Coding Interview Preparation Guide

Sorting, Searching & Hash Tables — Advanced

Advanced variations on sorting, searching and hashing: harder problems built on the basic toolkit.

Challenges on this page

INTERVIEW PREP TOPICS OVERVIEW

Optional, from another article: * Study fancier graph algorithms: Dijkstra and A* - they're really great. ADVICE: if you get any problem - first think graphs: fundamental and flexible way of representing relationships, 50-50 chance that any interesting design problem has a graph involved! * Learn data structures and algos, should especially know the most famous classes of NP-complete problems (traveling salesman and knapsack) => recognize them when in disguise; and what NP-complete means * Descrete Math (counting problems, probability problems, and other Discrete Math 101 situations) * OS: threads and concurrency; locks, mutexes, semaphores, monitors - how they work; deadlock, livelock - how to avoid. What resources a process needs, a thread needs, how context switching works, how it's initiated by OS and underlying hardware. Know a little about scheduling.

Data Structures and Algorithms: * Karya Umum Dsa; * Geeks for Geeks: 500 Data Structures and Algorithms; * Top 10 Algos in Interview Questions; * Big-O Cheat Sheet and Big-O notation. * Competitive Programmer's Handbook

Websites Recommended by Google Software Engineers for practice: * Top Coder: https://www.topcoder.com/ * HackerRank: https://www.hackerrank.com/ * Project Euler: https://projecteuler.net/ * LeetCode: LeetCode.com * Interview Cake: https://www.interviewcake.com/ * CodeFights https://codefights.com/

Second company:
Critical
* Arrays and lists * Stacks and queues * Binary trees, Graphs, Graph traversals: BFS, DFS * Search: iterator, binary, hash * Sort: merge, quick, bucket * Hash tables * Recursion * Complexity, Big O notation

Nice to Have * Trie * Red-black trees (theory) * Randomized quicksort (theory) * Spanning tree, minimum cut (theory) * Heap - part of heap sort (heapify) * Radix sort (theory) * Dynamic programming (However: there is no need to learn or know how to do dynamic programming or memorization) * Set

Another website for pratice: https://leetcode.com/problemset/all/

SORTING ALGORITHMS (nLogn)

Quick Sort (other verions)

Comment for the second solution

Python
# second in-place algo
def quick_sort2(array, start, end):
        
    if start >= end:
        return

    p = partition2(array, start, end)
    quick_sort2(array, start, p-1)
    quick_sort2(array, p+1, end)
    
    
def partition2(array, start, end):
        
    pivot = array[start]
    low = start + 1
    high = end

    while True:
        # if current value > pivot => OK, move left,keep track of low pointer (to know when all moved)
        while low <= high and array[high] >= pivot:
            high = high - 1

        # Opposite to the above
        while low <= high and array[low] <= pivot:
            low = low + 1

        # either found an out-of-order value for high and low OR low > high => exit loop
        if low <= high:
            array[low], array[high] = array[high], array[low]    # loop continues            
        else:            
            break                                                # exit loop

    array[start], array[high] = array[high], array[start]

    return high

Python
# is this really a 3-way quick sort? + how can I get rid of random?
# https://stackoverflow.com/questions/36972714/implementing-3-way-quicksort
import random
def quick_sort_3way(a, l, r):
    if l >= r:
        return
    k = random.randint(l, r)
    a[l], a[k] = a[k], a[l]
    m1, m2 = partition3(a, l, r)
    quick_sort_3way(a, l, m1 - 1)
    quick_sort_3way(a, m2 + 1, r)

def partition3(a, l, r):
    x, j, t = a[l], l, r
    i = j

    while i <= t :
        if a[i] < x:
            a[j], a[i] = a[i], a[j]
            j += 1

        elif a[i] > x:
            a[t], a[i] = a[i], a[t]
            t -= 1
            i -= 1 # remain in the same i in this case
        i += 1   
    return j, t
Python
# short version of quicksort - not in place, requires storage
def quick_sort3(_list):
    
    if len(_list) <= 1:
        return _list
    
    pivot = _list[len(_list) // 2]                       # it can also be first or last element of the list
    left = [x for x in _list if x < pivot]
    middle = [x for x in _list if x == pivot]
    right = [x for x in _list if x > pivot]
        
    return quick_sort3(left) + middle + quick_sort3(right)
Python
# test sorting f(x)
a, b, c, d = [8, 7, 4, 2, 1, 25, 29, 38, 45, 5, 101, 97, 73, 74, 72, 55], [8, 7, 4, 2, 1], [8, 7, 2, 11], [8, 7]
for arr in [a, b, c, d]:
    print(arr, end=' ')
    quick_sort(arr, 0, len(arr) - 1)      # in-place sorting    
    print('=> ', arr)
    #print('=> ', quick_sort3(arr))       # not in-place
Show output (151 lines)
Partition: pivot =  55
partition: i =  -1
Before last swap: [8, 7, 4, 2, 1, 25, 29, 38, 45, 5, 101, 97, 73, 74, 72, 55]
After last swap: [8, 7, 4, 2, 1, 25, 29, 38, 45, 5, 55, 97, 73, 74, 72, 101]

Partition: pivot =  5
partition: i =  -1
Before last swap: [4, 2, 1, 7, 8, 25, 29, 38, 45, 5, 55, 97, 73, 74, 72, 101]
After last swap: [4, 2, 1, 5, 8, 25, 29, 38, 45, 7, 55, 97, 73, 74, 72, 101]

Partition: pivot =  1
partition: i =  -1
Before last swap: [4, 2, 1, 5, 8, 25, 29, 38, 45, 7, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 8, 25, 29, 38, 45, 7, 55, 97, 73, 74, 72, 101]

Partition: pivot =  4
partition: i =  0
Before last swap: [1, 2, 4, 5, 8, 25, 29, 38, 45, 7, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 8, 25, 29, 38, 45, 7, 55, 97, 73, 74, 72, 101]

Recursive call for [1, 2]
Recursive call for [5, 8, 25, 29, 38, 45, 7, 55, 97, 73, 74, 72, 101]
Recursive call for []
Recursive call for [2, 4, 5, 8, 25, 29, 38, 45, 7, 55, 97, 73, 74, 72, 101]
Partition: pivot =  7
partition: i =  3
Before last swap: [1, 2, 4, 5, 8, 25, 29, 38, 45, 7, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 7, 25, 29, 38, 45, 8, 55, 97, 73, 74, 72, 101]

Partition: pivot =  8
partition: i =  4
Before last swap: [1, 2, 4, 5, 7, 25, 29, 38, 45, 8, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 7, 8, 29, 38, 45, 25, 55, 97, 73, 74, 72, 101]

Partition: pivot =  25
partition: i =  5
Before last swap: [1, 2, 4, 5, 7, 8, 29, 38, 45, 25, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 7, 8, 25, 38, 45, 29, 55, 97, 73, 74, 72, 101]

Partition: pivot =  29
partition: i =  6
Before last swap: [1, 2, 4, 5, 7, 8, 25, 38, 45, 29, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 7, 8, 25, 29, 45, 38, 55, 97, 73, 74, 72, 101]

Partition: pivot =  38
partition: i =  7
Before last swap: [1, 2, 4, 5, 7, 8, 25, 29, 45, 38, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 97, 73, 74, 72, 101]

Recursive call for [1, 2, 4, 5, 7, 8, 25, 29]
Recursive call for [45, 55, 97, 73, 74, 72, 101]
Recursive call for [1, 2, 4, 5, 7, 8, 25]
Recursive call for [38, 45, 55, 97, 73, 74, 72, 101]
Recursive call for [1, 2, 4, 5, 7, 8]
Recursive call for [29, 38, 45, 55, 97, 73, 74, 72, 101]
Recursive call for [1, 2, 4, 5, 7]
Recursive call for [25, 29, 38, 45, 55, 97, 73, 74, 72, 101]
Recursive call for [1, 2, 4, 5]
Recursive call for [8, 25, 29, 38, 45, 55, 97, 73, 74, 72, 101]
Recursive call for [1, 2, 4]
Recursive call for [7, 8, 25, 29, 38, 45, 55, 97, 73, 74, 72, 101]
Partition: pivot =  101
partition: i =  10
Before last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 97, 73, 74, 72, 101]

Partition: pivot =  72
partition: i =  10
Before last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 97, 73, 74, 72, 101]
After last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74, 97, 101]

Partition: pivot =  97
partition: i =  11
Before last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74, 97, 101]
After last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74, 97, 101]

Partition: pivot =  74
partition: i =  11
Before last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74, 97, 101]
After last swap: [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74, 97, 101]

Recursive call for [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73]
Recursive call for [97, 101]
Recursive call for [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74]
Recursive call for [101]
Recursive call for [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55]
Recursive call for [73, 74, 97, 101]
Recursive call for [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74, 97]
Recursive call for []
Recursive call for [1, 2, 4, 5, 7, 8, 25, 29, 38, 45]
Recursive call for [72, 73, 74, 97, 101]
=>  [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74, 97, 101]
Partition: pivot =  1
partition: i =  -1
Before last swap: [8, 7, 4, 2, 1]
After last swap: [1, 7, 4, 2, 8]

Partition: pivot =  8
partition: i =  0
Before last swap: [1, 7, 4, 2, 8]
After last swap: [1, 7, 4, 2, 8]

Partition: pivot =  2
partition: i =  0
Before last swap: [1, 7, 4, 2, 8]
After last swap: [1, 2, 4, 7, 8]

Partition: pivot =  7
partition: i =  1
Before last swap: [1, 2, 4, 7, 8]
After last swap: [1, 2, 4, 7, 8]

Recursive call for [1, 2, 4]
Recursive call for [8]
Recursive call for [1]
Recursive call for [4, 7, 8]
Recursive call for [1, 2, 4, 7]
Recursive call for []
Recursive call for []
Recursive call for [2, 4, 7, 8]
=>  [1, 2, 4, 7, 8]
Partition: pivot =  11
partition: i =  -1
Before last swap: [8, 7, 2, 11]
After last swap: [8, 7, 2, 11]

Partition: pivot =  2
partition: i =  -1
Before last swap: [8, 7, 2, 11]
After last swap: [2, 7, 8, 11]

Partition: pivot =  8
partition: i =  0
Before last swap: [2, 7, 8, 11]
After last swap: [2, 7, 8, 11]

Recursive call for [2, 7]
Recursive call for [11]
Recursive call for []
Recursive call for [7, 8, 11]
Recursive call for [2, 7, 8]
Recursive call for []
=>  [2, 7, 8, 11]
Partition: pivot =  7
partition: i =  -1
Before last swap: [8, 7]
After last swap: [7, 8]

Recursive call for []
Recursive call for [8]
=>  [7, 8]

Bucket Sort

Useful when input uniformly distributed over a range
Time c. O(n) on average if all numbers are uniformly distributed
1) Create n empty buckets (Or lists).
2) Insert every arr[i] into bucket[n*arr[i]]
3) Sort individual buckets using insertion sort.
4) Concatenate all sorted buckets.

Python
# https://www.techconductor.com/algorithms/python/Sort/Bucket_Sort.php
import math

def insertion_sort(bucket):
        
    for i in range(1, len(bucket)):
                
        key = bucket[i] 
        j = i - 1
        while j >=0 and bucket[j] > key:  
            bucket[j + 1] = bucket[j] 
            j -= 1
        bucket[j + 1] = key
                
    return bucket      


def bucket_sort(arr, bucket_size = DEFAULT_BUCKET_SIZE):
        
    if len(arr) == 0: return[]

    minValue = arr[0]                                                                  # finding min and max
    maxValue = arr[0]
        
    for i in range(0, len(arr)):
        if arr[i] < minValue:
            minValue = arr[i]
        elif arr[i] > maxValue:
            maxValue = arr[i]
    
    buckets = []    
    bucket_count = math.floor((maxValue - minValue) / bucket_size) + 1                      # Initialize buckets
        
    for i in range(0, bucket_count):
        buckets.append([])
    
    for i in range(0, len(arr)):                                                       # put values in buckets
        buckets[math.floor((arr[i] - minValue) / bucket_size)].append(arr[i])
    
    arr = []
    for i in range(0, len(buckets)):                                                   # Sort buckets and place back into array
        buckets[i] = insertion_sort(buckets[i])
        for j in range(0, len(buckets[i])):
            arr.append(buckets[i][j])

    return arr


DEFAULT_BUCKET_SIZE = 5
arr2 = [12, 23, 4, 5, 3, 2, -12, 81, 56, 95]
sortedArray = bucket_sort(arr2)
print(sortedArray)
Output
[-12, 2, 3, 4, 5, 12, 23, 56, 81, 95]

Radix sort

Digit by digit sort starting from least significant digit to most significant digit. Use counting sort to sort individual digits.
Steps:
For each digit i (least significant digit < i < most significant digit): sort input array using counting sort on the i’th digit

Radix sort is an integer sorting algorithm that sorts data with integer keys by grouping the keys by individual digits that share the same significant position and value (place value). Radix sort uses counting sort as a subroutine to sort an array of numbers. Because integers can be used to represent strings (by hashing the strings to integers), radix sort works on data types other than just integers. Because radix sort is not comparison based, it is not bounded by \Omega(n \log n)Ω(nlogn) for running time — in fact, radix sort can perform in linear time

image.png

image.png

image.png

Python
# Using counting sort to sort the elements in the basis of significant places
def countingSort(array, place):
    size = len(array)
    output = [0] * size
    count = [0] * 10

    # Calculate count of elements
    for i in range(0, size):
        index = array[i] // place
        count[index % 10] += 1

    # Calculate cummulative count
    for i in range(1, 10):
        count[i] += count[i - 1]

    # Place the elements in sorted order
    i = size - 1
    while i >= 0:
        index = array[i] // place
        output[count[index % 10] - 1] = array[i]
        count[index % 10] -= 1
        i -= 1

    for i in range(0, size):
        array[i] = output[i]


# Main function to implement radix sort
def radixSort(array):
    # Get maximum element
    max_element = max(array)

    # Apply counting sort to sort elements based on place value.
    place = 1
    while max_element // place > 0:
        countingSort(array, place)
        place *= 10


data = [121, 432, 564, 23, 1, 45, 788]
radixSort(data)
print(data)
Output
[1, 23, 45, 121, 432, 564, 788]

SEARCH ALGORITHMS

Idea - return higher value of pos when searched value closer to arr[hi], and smaller when closer to arr[lo]
pos = lo + [ (x-arr[lo])*(hi-lo) / (arr[hi]-arr[Lo]) ]

On average the interpolation search makes about log(log(n)) comparisons (if the elements are uniformly distributed), where n is the number of elements to be searched. In the worst case (for instance where the numerical values of the keys increase exponentially) it can make up to O(n) comparisons

O(1) space complexity

Usage: improvement over Binary Search for instances, where the values in a sorted array are uniformly distributed. Binary goes to the middle while interpolation may go closer the element to be found. Formula for position:
pos = lo + [ (x-arr[lo])*(hi-lo) / (arr[hi]-arr[Lo]) ]

arr[] ==> Array where elements need to be searched
x ==> Element to be searched
lo ==> Starting index in arr[]
hi ==> Ending index in arr[]

Python
# If x is present in arr[0..n-1], then returns 
# index of it, else returns -1 
def interpolationSearch(arr, n, x): 
    # Find indexs of two corners 
    lo = 0
    hi = (n - 1) 
   
    # Since array is sorted, an element present 
    # in array must be in range defined by corner 
    while lo <= hi and x >= arr[lo] and x <= arr[hi]: 
        if lo == hi: 
            if arr[lo] == x:  
                return lo; 
            return -1; 
          
        # Probing the position with keeping 
        # uniform distribution in mind. 
        pos  = lo + int(((float(hi - lo) / 
            ( arr[hi] - arr[lo])) * ( x - arr[lo]))) 
  
        # Condition of target found 
        if arr[pos] == x: 
            return pos 
   
        # If x is larger, x is in upper part 
        if arr[pos] < x: 
            lo = pos + 1; 
   
        # If x is smaller, x is in lower part 
        else: 
            hi = pos - 1; 
      
    return -1
Python
# Driver Code 
# Array of items oin which search will be conducted 
arr = [10, 12, 13, 16, 18, 19, 20, 21, 22, 23, 24, 33, 35, 42, 47] 
n = len(arr) 
  
x = 18 # Element to be searched 
index = interpolationSearch(arr, n, x) 
  
if index != -1: 
    print("Element found at index", index) 
else: 
    print("Element not found")
Output
Element found at index 4

Like Binary Search, Jump Search is a searching algorithm for sorted arrays. The basic idea is to check fewer elements (than linear search) by jumping ahead by fixed steps or skipping some elements in place of searching all elements.

For example, suppose we have an array arr[] of size n and block (to be jumped) size m. Then we search at the indexes arr[0], arr[m], arr[2m]…..arr[km] and so on. Once we find the interval (arr[km] < x < arr[(k+1)m]), we perform a linear search operation from the index km to find the element x.

Let’s consider the following array: (0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610). Length of the array is 16. Jump search will find the value of 55 with the following steps assuming that the block size to be jumped is 4. STEP 1: Jump from index 0 to index 4; STEP 2: Jump from index 4 to index 8; STEP 3: Jump from index 8 to index 12; STEP 4: Since the element at index 12 is greater than 55 we will jump back a step to come to index 8. STEP 5: Perform linear search from index 8 to get the element 55.

What is the optimal block size to be skipped? In the worst case, we have to do n/m jumps and if the last checked value is greater than the element to be searched for, we perform m-1 comparisons more for linear search. Therefore the total number of comparisons in the worst case will be ((n/m) + m-1). The value of the function ((n/m) + m-1) will be minimum when m = √n. Therefore, the best step size is m = √n.

Important points:

Works only sorted arrays. The optimal size of a block to be jumped is (√ n). This makes the time complexity of Jump Search O(√ n). The time complexity of Jump Search is between Linear Search ( ( O(n) ) and Binary Search ( O (Log n) ). Binary Search is better than Jump Search, but Jump search has an advantage that we traverse back only once (Binary Search may require up to O(Log n) jumps, consider a situation where the element to be searched is the smallest element or smaller than the smallest). So in a system where binary search is costly, we use Jump Search

Python
import math

def jumpSearch( arr , x , n ): 
      
    # Finding block size to be jumped 
    step = math.sqrt(n) 
      
    # Finding the block where element is 
    # present (if it is present) 
    prev = 0
    while arr[int(min(step, n)-1)] < x: 
        prev = step 
        step += math.sqrt(n) 
        if prev >= n: 
            return -1
      
    # Doing a linear search for x in  
    # block beginning with prev. 
    while arr[int(prev)] < x: 
        prev += 1
          
        # If we reached next block or end  
        # of array, element is not present. 
        if prev == min(step, n): 
            return -1
      
    # If element is found 
    if arr[int(prev)] == x: 
        return prev 
      
    return -1
Python
# Driver code to test function 
arr = [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 
    34, 55, 89, 144, 233, 377, 610 ] 
x = 55
n = len(arr) 
  
# Find the index of 'x' using Jump Search 
index = jumpSearch(arr, x, n) 
  
# Print the index where 'x' is located 
print("Number" , x, "is at index" ,"%.0f"%index) 
Output
Number 55 is at index 10

Find range where element is present Do Binary Search in above found range. How to find the range where element may be present? The idea is to start with subarray size 1, compare its last element with x, then try size 2, then 4 and so on until last element of a subarray is not greater. Once we find an index i (after repeated doubling of i), we know that the element must be present between i/2 and i (Why i/2? because we could not find a greater value in previous iteration)

Time Complexity : O(Log n) Auxiliary Space : The above implementation of Binary Search is recursive and requires O(Log n) space. With iterative Binary Search, we need only O(1) space.

Applications of Exponential Search:

Exponential Binary Search is particularly useful for unbounded searches, where size of array is infinite. Please refer Unbounded Binary Search for an example. It works better than Binary Search for bounded arrays, and also when the element to be searched is closer to the first element.

Python
# Returns the position of first 
# occurrence of x in array 
def exponentialSearch(arr, n, x): 
    # IF x is present at first  
    # location itself 
    if arr[0] == x: 
        return 0
          
    # Find range for binary search  
    # j by repeated doubling 
    i = 1
    while i < n and arr[i] <= x: 
        i = i * 2
      
    # Call binary search for the found range 
    return binarySearch( arr, i / 2,  
                         min(i, n), x)
Python
# Driver Code 
arr = [2, 3, 4, 10, 40] 
n = len(arr) 
x = 10
result = exponentialSearch(arr, n, x) 
if result == -1: 
    print "Element not found in thye array"
else: 
    print "Element is present at index %d" %(result) 

Binary vs. Linear

Binary search does less comparisons than tertiary

The following is recursive formula for counting comparisons in worst case of Binary Search.

T(n) = T(n/2) + 2, T(1) = 1 The following is recursive formula for counting comparisons in worst case of Ternary Search.

T(n) = T(n/3) + 4, T(1) = 1 In binary search, there are 2Log2n + 1 comparisons in worst case. In ternary search, there are 4Log3n + 1 comparisons in worst case.

Time Complexity for Binary search = 2clog2n + O(1) Time Complexity for Ternary search = 4clog3n + O(1) Therefore, the comparison of Ternary and Binary Searches boils down the comparison of expressions 2Log3n and Log2n . The value of 2Log3n can be written as (2 / Log23) * Log2n . Since the value of (2 / Log23) is more than one, Ternary Search does more comparisons than Binary Search in worst case

Selection Sort

Based on https://github.com/agnedil/Code-Python-Algorithms-Notebooks/blob/master/ipython_nbs/sorting/selection_sort.ipynb

Python
# sorting f(x)
def selection_sort(myarr):
    
    # iterate over the array
    for i in range (len(myarr)-1):
        min_idx = i + 1
        
        # find the smallest element on the right excluding the leftmost element
        for j in range(i + 2, len(myarr)):
            if myarr[j] < myarr[min_idx]:
                min_idx = j
                
        # compare the smallest element found on the right w/the leftmost element and swap if smaller
        if myarr[min_idx] < myarr[i]:
            myarr[min_idx], myarr[i] = myarr[i], myarr[min_idx]
            
    return myarr
Python
# test sorting f(x)
a, b, c, d = [8, 7, 4, 2, 1, 25, 29, 38, 45, 5, 101, 97, 73, 74, 72, 55], [8, 7, 4, 2, 1], [8, 7, 2, 11], [8, 7]
for arr in [a, b, c, d]:
    print(arr, end=' ')
    print('=> ', merge_sort(arr))
Output
[8, 7, 4, 2, 1, 25, 29, 38, 45, 5, 101, 97, 73, 74, 72, 55] =>  [1, 2, 4, 5, 7, 8, 25, 29, 38, 45, 55, 72, 73, 74, 97, 101]
[8, 7, 4, 2, 1] =>  [1, 2, 4, 7, 8]
[8, 7, 2, 11] =>  [2, 7, 8, 11]
[8, 7] =>  [7, 8]

Edit Levenshtein Distance (another solution)

https://stackoverflow.com/questions/2460177/edit-distance-in-python https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python

Python
# wiki
def levenshtein(s1, s2):
    if len(s1) < len(s2):
        return levenshtein(s2, s1)

    # len(s1) >= len(s2)
    if len(s2) == 0:
        return len(s1)

    previous_row = range(len(s2) + 1)
    for i, c1 in enumerate(s1):
        current_row = [i + 1]
        for j, c2 in enumerate(s2):
            insertions    = previous_row[j + 1] + 1       # j+1 instead of j since previous_row and current_row are one char longer            
            substitutions = previous_row[j] + (c1 != c2)
            deletions     = current_row[j] + 1             # than s2
            current_row.append(min(insertions, deletions, substitutions))
        previous_row = current_row
        print(previous_row)
    
    return previous_row[-1]
Python
levenshtein('aborigenous', 'sc')
Output
[1, 1, 2]
[2, 2, 2]
[3, 3, 3]
[4, 4, 4]
[5, 5, 5]
[6, 6, 6]
[7, 7, 7]
[8, 8, 8]
[9, 9, 9]
[10, 10, 10]
[11, 10, 11]
11