Search algorithms form an important part of many programs. Some searches involve looking for an entry in a database, such as looking up your record in the IRS database. Other search algorithms trawl through a virtual space, such as those hunting for the best chess moves. Follow along and learn 18 Searching Algorithms Interview Questions and Answers experienced developers shall brush before next coding interview.
When the list is sorted we can use the binary search (also known as half-interval search, logarithmic search, or binary chop) technique to find items on the list. Here's a step-by-step description of using binary search:
min = 1 and max = n.max and min rounded down so that it is an integer.In this example we looking for array item with value 4:
When you do one operation in binary search we reduce the size of the problem by half (look at the picture below how do we reduce the size of the problem area) hence the complexity of binary search is O(log n). The binary search algorithm can be written either recursively or iteratively.
var binarySearch = function(array, value) {
var guess,
min = 0,
max = array.length - 1;
while(min <= max){
guess = Math.floor((min + max) /2);
if(array[guess] === value)
return guess;
else if(array[guess] < value)
min = guess + 1;
else
max = guess - 1;
}
return -1;
}Linear (sequential) search goes through all possible elements in some array and compare each one with the desired element. It may take up to O(n) operations, where N is the size of an array and is widely considered to be horribly slow. In linear search when you perform one operation you reduce the size of the problem by one (when you do one operation in binary search you reduce the size of the problem by half). Despite it, it can still be used when:
O(1)When you ask MySQL something like SELECT x FROM y WHERE z = t, and z is a column without an index, linear search is performed with all the consequences of it. This is why adding an index to searchable columns is important.
n comparisons, where n is the length of the list. If each element is equally likely to be searched, then linear search has an average case of (n+1)/2 comparisons, but the average case can be affected if the search probabilities for each element vary.O(1)function linearSearch(array, toFind){
for(let i = 0; i < array.length; i++){
if(array[i] === toFind) return i;
}
return -1;
}>, <); linear search only requires equality comparisons (=)O(log n); linear search has complexity O(n)The idea behind linear search is to compare the search item with the elements in the list one by one (using a loop) and stop as soon as we get the first copy of the search element in the list. Now considering the worst case in which the search element does not exist in the list of size N then the Simple Linear Search will take a total of 2N+1 comparisons (N comparisons against every element in the search list and N+1 comparisons to test against the end of the loop condition).
for (int i = 0; i < length; i++) { // N+1 comparisons
if (array[i] == elementToSearch) { // N comparisons
return i; // I found the position of the element requested
}
}The idea of Sentinel Linear Search is to reduce the number of comparisons required to find an element in a list. Here we replace the last element of the list with the search element itself and run a while loop to see if there exists any copy of the search element in the list and quit the loop as soon as we find the search element. This will reduce one comparison in each iteration.
while(a[i] != element) // N+2 comparisons worst case
i++;In that case the while loop makes only one comparison in each iteration and it is sure that it will terminate since the last element of the list is the search element itself. So in the worst case ( if the search element does not exists in the list ) then there will be at most N+2 comparisons (N comparisons in the while loop and 2 comparisons in the if condition). Which is better than (2N+1) comparisons as found in Simple Linear Search.
Take note that both the algorithms (Simple Linear and Sentinel) have time complexity of O(n).
/// <summary>
/// Gets the index of first founded item
/// </summary>
/// <typeparam name="T">The type of array</typeparam>
/// <param name="array">Input array</param>
/// <param name="value">Value of searching item</param>
/// <param name="equalityComparer">Custom comparer</param>
/// <returns>The index of founded item or -1 if not found</returns>
public static int BetterLinearSearch < T > (this IEnumerable < T > array, T value, IEqualityComparer < T > equalityComparer = null) {
if (equalityComparer == null) equalityComparer = new DefaultEqualityComparer < T > ();
int arrayLength = array.Count();
T lastItem = array.Last();
array = array.SetElement(arrayLength - 1, value);
int index = 0;
while (!equalityComparer.Equals(array.ElementAt(index), value))
index++;
if (index < arrayLength - 1) return index;
if (equalityComparer.Equals(lastItem, value)) return arrayLength - 1;
return - 1;
}If the elements you would be searching in a list are NOT uniformly distributed, then you could do certain optimizations that can help you improve the efficiency of your search algorithms. For such improvements to be effective, you do need to know the characteristics of the data being searched, the frequency and also the fact that the underlying collection needs to be modifiable.
A[0, i-1] to A[1, i] to accommodate the new element at A[0].x with the first element at position 0. This eventually results in a similar pattern as the frequently searched elements bubble up to the top of the list.Interpolation Search is an algorithm similar to Binary Search for searching for a given target value in a sorted array. It is an improvement over Binary Search for instances, where the values in a sorted array are uniformly distributed.
The binary search always chooses the middle of the remaining search space. On a contrast Interpolation search, at each search step, calculates (using interpolation formula) where in the remaining search space the target might be present, based on the low and high values of the search space and the value of the target. For example, if the value of the key is closer to the last element, interpolation search is likely to start search toward the end side. The value actually found at this estimated position is then compared to the target value. If it is not equal, then depending on the comparison, the remaining search space is reduced to the part before or after the estimated position.
The idea of the interpolation formula or partitioning logic is to return higher value of pos when element to be searched is closer to arr[hi]. And smaller value when closer to arr[low].
arr[] ==> Array where elements need to be searched
x ==> Element to be searched
low ==> Starting index in arr[]
hi ==> Ending index in arr[]
pos = low + [ (x-arr[low])*(hi-low) / (arr[hi]-arr[Low]) ]In the interpolation search algorithm, the midpoint is computed in such a way that it gives a higher probability of obtaining our search term faster. The worst case complexity of interpolation search is O(n). However, its average case complexity, under the assumption that the keys are uniformly distributed, is _O(log log N).
It can be proven that Interpolation Search repeatedly reducing the problem to a subproblem of size that is the square root of the original problem size, and algorithm will terminate after O(log log n) steps (see proof here). When the values are equally dispersed into the interval this search algorithm can be extremely useful – way faster than the binary search (that divides searching space by half).
const interpolationSearch = (array, key) => {
// if array is empty.
if (!array.length) {
return -1;
}
let low = 0;
let high = array.length - 1;
while (low <= high && key >= array[low] && x <= array[high]) {
// calculate position with
let pos = low + Math.floor(((high - low) * (key - array[low])) / (array[high] - array[low]));
// if all elements are same then we'll have divide by 0 or 0/0
// which may cause NaN
pos = Number.isNaN(pos) ? low : pos;
if (array[pos] === key) {
return pos;
}
if (array[pos] > key) {
high = pos - 1;
} else {
low = pos + 1;
}
}
// not found.
return -1;
};O(log n)?When you do one operation in binary search we reduce the size of the problem by half. To answer the questions we have to answer, how many times can you divide N by 2 until you have 1? This is essentially saying, do a binary search (half the elements) until you found it. In a formula this would be this:
1 = N / 2x
multiply by 2x:
2x = N
now do the log2:
log2(2x) = log2 N
x log2(2) = log2 N
x 1 = log2 N
this means you can divide log N times until you have everything divided. Which means you have to divide log N ("do the binary search step") until you found your element.
The same concept explained graphically:
Jump Search (also referred to as Block Search) is an algorithm used to search for the position of a target element on a sorted data collection or structure.
The fundamental idea behind this searching technique is to search fewer number of elements compared to linear search algorithm (which scans every element in the array to check if it matches with the element being searched or not). This can be done by skipping some fixed number of array elements or jumping ahead by fixed number of steps in every iteration.
Lets consider a sorted array A[] of size n, with indexing ranging between 0 and n-1, and element x that needs to be searched in the array A[]. For implementing this algorithm, a block of size m is also required, that can be skipped or jumped in every iteration. Thus, the algorithm works as follows:
x==A[0]), then success, else, if (x > A[0]), then jump to the next block.x==A[m]), then success, else, if (x > A[m]), then jump to the next block.x==A[2m]), then success, else, if (x > A[2m]), then jump to the next block.x < A[km]), then a linear search is performed from index A[(k-1)m] to A[km]Consider the following inputs:
A[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 77, 89, 101, 201, 256, 780}item = 77/).O(log n) complexity and Linear Search with an O(n) complexity. n/m number of jumps. Additionally, if the last element’s value of this block was greater than the target element, Jump Search would perform a linear search with m-1 iterations.(n/m) jumps with additional m-1 iterations. This value is minimum at m = √n. Therefore, the optimum block size is √n. Accordingly, Jump Search maintains a worst and average case efficiency of O(√n) complexity.O(1) since it does not require any additional temporary variables or data structures for its implementation.function jumpSearch(arrayToSearch, valueToSearch){
var length = arrayToSearch.length;
var step = Math.floor(Math.sqrt(length));
var index = Math.min(step, length)-1;
var lowerBound = 0;
while (arrayToSearch[Math.min(step, length)-1] < valueToSearch)
{
lowerBound = step;
step += step;
if (lowerBound >= length){
return -1;
}
}
var upperBound = Math.min(step, length);
while (arrayToSearch[lowerBound] < valueToSearch)
{
lowerBound++;
if (lowerBound == upperBound){
return -1;
}
}
if (arrayToSearch[lowerBound] == valueToSearch){
return lowerBound;
}
return -1;
}I understand that interpolation search not only requires a sorted list but also uniformly distributed. Provide an understandable situation where Interpolation search is slower than binary search.
If we assume uniform distribution of the values so we'll use simple linear interpolation. So if the values are:
1,2,3,4,5,6,7,8,9,10000000And we search for number 9, searching using linear interpolation will go through all (excluding the first and last) the indices before finding the correct one.
In such cases interpolation search will be O(n).
Consider:
bool recur_search(int key, int values[], int lower, int upper)
{
// base case 1: not in haystack
if (upper < lower)
{
return false;
}
int mid = (lower + upper) / 2;
// base case 2: found
if (key == values[mid])
{
return true;
}
// recursive cases
else if (key < values[mid])
{
recur_search(key, values, lower, mid - 1);
}
else // (key > values[mid])
{
recur_search(key, values, mid + 1, upper);
}
// to get rid of "error: control may reach end of non-void function"
return false;
}What's wrong with this code?
Classic mistake!
Even though it is calling itself correctly, it is missing the code necessary to recursively return the result. As it is written above, it will execute the recursive call to itself, but when it does find a number, it will return true on the first step back through the recursion. However, at the second step and thereafter, the code will return to the calling recursion and process the next line of code because it has no instruction to do something else. It will drop through the if(upper < lower) block and hit the return false statement. So, from there on, it will just return false back up the recursion.
The addition of one keyword, return, in two places, fixes the code:
// recursive cases
...
else if (key < values[mid])
{
return recur_search(key, values, lower, mid - 1);
}
else // (key > values[mid])
{
return recur_search(key, values, mid + 1, upper);
}
...O(n) time?O(log n) on a sorted Linked List?Rust has been Stack Overflow’s most loved language for four years in a row and emerged as a compelling language choice for both backend and system developers, offering a unique combination of memory safety, performance, concurrency without Data races...
Clean Architecture provides a clear and modular structure for building software systems, separating business rules from implementation details. It promotes maintainability by allowing for easier updates and changes to specific components without affe...
Azure Service Bus is a crucial component for Azure cloud developers as it provides reliable and scalable messaging capabilities. It enables decoupled communication between different components of a distributed system, promoting flexibility and resili...