About usWhy usInstructorsReviewsCostFAQContactBlogRegister for Webinar

Find Range Problem

Given an array of integers sorted in ascending order, find the first and the last occurrence of a given target number in the array.

Example One:

Input: [1, 2, 3, 3, 3, 4, 5, 5], 3

Output: [2, 4]

Example Two:

Input: [2, 3, 5, 10], 4

Output: [-1, -1]

Notes:

The function returns an array of size two where the array elements specify the starting and ending indices of the target value respectively in the given array.

In case when the target value is not present in the array, return the range as [-1, -1].

Constraints:

1

-109

-109

Solution

We provided two solutions.

We will start with a brute force solution and will then look at some observations to arrive at a more optimized solution using the binary search algorithm. Throughout this editorial, we will refer to the input array as numbers, its size as numbers_count and the target integer as target.

1) linear_scan_solution.cpp

Let us call the index of the first and last occurrence of the target number as first_index and last_index respectively.

To find the first_index, we will iterate the numbers array from left to right and stop as we find the first occurrence of the target from left. If while iterating, we reach a number greater than target, we don’t need to search further as the array is sorted. Similarly, to find the last_index, we will iterate the numbers array from right to left and stop as we find the first occurrence of the target from right.

But what if the target number is not present in the numbers array?

As mentioned in the problem statement, in that case we will return an array of size 2 with both the values equal to -1.

As a constant optimization, instead of running two separate loops for finding out the first and last occurrences of the target, we can find both of them in a single traversal of the array.

For this, we will initialize first_index and last_index as -1 and will simultaneously traverse the array both from the beginning and from the end. Say, we have two iterators left and right with left initialized to 0 and right initialized to numbers_size - 1. In each iteration we will check if numbers[left] or numbers[right] is equal to the target and if any of them is, then we will update the left_index and right_index accordingly. At the end of each iteration, we will increment left and decrement right. We will break the loop when one of the following conditions get satisfied:

● Both first_index and last_index are updated from -1.

● left_index > right_index.

Time Complexity:

O(numbers_count).

We needed to run two linear loops on the numbers array which in the worst case will scan the entire array once in total.

Auxiliary Space Used:

O(1).

We used a constant amount of additional memory.

Space Complexity:

Space used for input: O(numbers_count).

Auxiliary space used: O(1).

Space used for output: O(1).

So, total space complexity: O(numbers_count).


// -------- START --------

vector < int > find_range(vector < int > numbers, int target) {
    vector < int > range(2);

    int first_index = -1;
    for (int i = 0; i < numbers.size(); ++i) {
        if (numbers[i] == target) {
            first_index = i;
            break;
        }
        // If the number is greater than target, we do not need to search further as the array is sorted.
        if (numbers[i] > target) {
            break;
        }
    }

    // If first_index is not updated, it means that the target value is not present in the array.
    if (first_index == -1) {
        range[0] = range[1] = -1;
        return range;
    }

    int last_index = -1;
    for (int i = numbers.size() - 1; i >= 0; --i) {
        if (numbers[i] == target) {
            last_index = i;
            break;
        }
    }

    range[0] = first_index;
    range[1] = last_index;
    return range;
}

// -------- END --------

2) binary_search_solution.cpp

Using the fact that the numbers array is sorted, we can use the Binary Search algorithm to find the leftmost and the rightmost index of the target integer in the array. Similar to the first solution, we will refer to the first and last index of the target in the array as first_index and last_index.

To find the first_index, we will perform a modified binary search on the array. In the normal binary search, we stop when we find the target number in the array. Here, instead of stopping, we will store the current index in first_index and continue searching in the left subarray to check if there is another occurrence of target in any of the smaller indices of the array. The reason behind searching in the left subarray is that, if we find the target number on the current index, we cannot be sure whether it is the first occurrence of target or not. We are storing the current index in first_index to cover the case of it being the first occurrence and searching in the left subarray to cover the case of it not being the first occurrence.

Also, we will initialize left_index with -1 to check whether the target is present in the array or not. If it remains equal to -1 after the above modified binary search, it means that the target is not present in the array and we will simply return [-1, -1] in that case.

We can perform a similar modified binary search algorithm to find the last_index as well.

Time Complexity:

O(log(numbers_count)).

We perform two binary searches on an array of size numbers_count. So, the time complexity of the function that we are supposed to fill is O(log(numbers_count)).

Though, the time complexity of the complete code will be O(numbers_count) as we will require that amount of time to take the input of numbers_count number of integers.

Auxiliary Space Used:

O(1).

We used a constant amount of additional memory.

Space Complexity:

Space used for input: O(numbers_count).

Auxiliary space used: O(1).

Space used for output: O(1).

So, total space complexity: O(numbers_count).


// -------- START --------

int starting_index(vector < int > numbers, int target) {
    int low = 0, high = numbers.size() - 1, mid;

    int first_index = -1;

    while (low <= high) {
        mid = (low + high) / 2;

        // If the current element is equal to the target, we will update the first_index and search in the
        // left half to check for a smaller value of first_index.
        if (numbers[mid] == target) {
            first_index = mid;
            high = mid - 1;
        } else if (numbers[mid] > target) {
            high = mid - 1;
        } else {
            low = mid + 1;
        }
    }

    return first_index;
}

int ending_index(vector < int > & numbers, int low, int target) {
    int high = numbers.size() - 1, mid;

    int last_index = -1;

    while (low <= high) {
        mid = (low + high) / 2;

        // If the current element is equal to the target, we will update the last_index and search in the
        // right half to check for a larger value of last_index.
        if (numbers[mid] == target) {
            last_index = mid;
            low = mid + 1;
        } else if (numbers[mid] > target) {
            high = mid - 1;
        } else {
            low = mid + 1;
        }
    }

    return last_index;
}

vector < int > find_range(vector < int > & numbers, int target) {
    vector < int > range(2);

    int first_index = -1;
    first_index = starting_index(numbers, target);

    // If first_index is equal to -1 after the above update,
    // it means that the target value is not present in the array.
    if (first_index == -1) {
        range[0] = range[1] = -1;
        return range;
    }

    int last_index = -1;
    // 'low' for the binary search for finding the ending index can be initialized to 'first_index'
    // as the ending index will be greater than or equal to the 'first_index'.
    last_index = ending_index(numbers, first_index, target);

    range[0] = first_index;
    range[1] = last_index;
    return range;
}

// -------- END --------

Attend our Free Webinar on How to Nail Your Next Technical Interview

Recommended Posts

All Posts