[algorithm LeetCode] 34. Find the first and last positions of elements in the sorted array (indexOf; binary search)

34. Find the first and last position of the element in the sorted array - LeetCode

Released: October 2, 2021 19:05:59

Problem description and examples

Given an integer array nums arranged in ascending order and a target value target. Find the start and end positions of the given target value in the array.

If the target value target does not exist in the array, return [- 1, - 1].

Advanced:

  • Can you design and implement an algorithm with time complexity of O(log n) to solve this problem?

Example 1:
Input: num = [5,7,7,8,8,10], target = 8
Output: [3,4]

Example 2:
Input: num = [5,7,7,8,8,10], target = 6
Output: [- 1, - 1]

Example 3:
Input: num = [], target = 0
Output: [- 1, - 1]

Tips:
0 <= nums.length <= 109
-109 <= nums[i] <= 109
nums is a non decreasing group
-109 <= target <= 109

Source: LeetCode
Link: https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array
The copyright belongs to Lingkou network. For commercial reprint, please contact the official authorization, and for non-commercial reprint, please indicate the source.

My solution

My solution 1 (indexOf and lastIndexOf)

Since it's done in JavaScript, naturally, the easiest way to think of is this method. Try it, it's really OK~ 🤣, Moreover, the performance performance is not as hip pulling as expected. I don't have much explanation. I'm afraid of strokes

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function(nums, target) {
  return [nums.indexOf(target), nums.lastIndexOf(target)];
};

Submit records
88 / 88 Pass test cases
 Status: passed
 Execution time: 76 ms, At all JavaScript Defeated 46 in submission.00%User
 Memory consumption: 39 MB, At all JavaScript Defeated 84 in submission.75%User
 Time: 2021/10/02 19:12

My problem solution 2 (violent solution)

This solution is also easier to understand. The first while loop is used to find the position where the target first appears in the array, and the second while loop is used to find the position where the target last appears in the array.

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function(nums, target) {
  let index = 0;
  let result = [-1, -1];
  // The first 'while' loop is used to find the position where 'target' appears for the first time in the array
  while(index < nums.length) {
    if(nums[index] === target) {
      result[0] = index;
      // Once the target is found, immediately end the while loop with break
      break;
    }
    index++;
  }
  // Don't forget to reset the value of index
  index = nums.length - 1;
  // The second 'while' loop is used to find the position of the last occurrence of 'target' in the array
  // Note that the end condition here is not simply written as index > = 0, because result[1] must not be smaller than result[0]
  while(index >= result[0]) {
    if(nums[index] === target) {
      result[1] = index;
      // Once the target is found, immediately end the while loop with break
      break;
    }
    index--;
  }
  return result;
};


Submit records
88 / 88 Pass test cases
 Status: passed
 Execution time: 72 ms, At all JavaScript Defeated 64 in submission.86%User
 Memory consumption: 39.1 MB, At all JavaScript Defeated 69 in submission.58%User
 Time: 2021/10/02 19:39

My solution 3 (binary search)

Neither of the above two solutions makes use of the problem feature that [num is arranged in ascending order]. According to this feature, we can use the idea of binary search to search the target.

I did a binary search before:

reference resources: [algorithm LeetCode] 704. Binary search_ Lai nianan's blog - CSDN blog

The core idea of binary search is the same as the pure binary search above. The only difference is that after finding the target, we should further use the dichotomy idea to find the left boundary and right boundary of the target interval.

After finding the target, continue to find the left boundary and the right boundary respectively

Note that the left and right and mid pointers are reusable.

The idea of this topic can be divided into three parts:

  1. The front, back and mid pointers are used to search for the target according to the conventional binary search idea, and the mid pointer is used to point to the searched target. (corresponding to the outermost while loop)
  2. After finding the target, save the state of the front and back pointers, and use the left, right and mid pointers to continue to follow the binary search idea to search the left boundary in the left half of the target found in [1]. (corresponding to the first while loop of the inner layer)
  3. After finding the left boundary, continue to search the right boundary according to the routine of [2] by using the previously saved state of front and back pointers. (corresponding to the second while loop of the inner layer)

The binary search end conditions in the three parts are slightly different.

For the idea of binary search, you can check the blog above. Please see the notes below for a detailed explanation of this topic:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function(nums, target) {
  // front and back play the same role as pure binary search
  let front = 0;
  let back = nums.length - 1;
  // Left and right can be regarded as two temporary auxiliary pointers to determine the left and right boundaries,
  // Its initial value doesn't matter, because it will be overwritten before it is used, and they can be reused
  let left = 0;
  let right = 0;
  // mid is used to indicate the middle position of an interval. It can also be reused
  let mid = 0;
  // Result is used to store the final result and is initialized to [- 1, - 1]
  let result = [-1, -1];
  // The outermost while loop is used to initially locate the target, but the left and right boundaries cannot be accurately determined,
  // Here is the part of [step 1]
  while(front <= back) {
    mid = Math.floor((back - front) / 2 + front);
    // The logic in the following if judgment is to continue searching for the left and right boundaries after finding the target (i.e. steps 2 and 3)
    if(nums[mid] === target) {
      // First search the left boundary. Here is the part of [step 2]
      left = front;
      right = mid;
      while(!(nums[mid] === target && nums[mid - 1] !== target)) {
        // [tag1] note that Math.floor should be used instead of Math.ceil. Please see [Supplement 1] below for details
        mid = Math.floor((right - left) / 2 + left);
        left = nums[mid] === target ? left : mid;
        right = nums[mid] === target ? mid : right;
      }
      // After the while loop above ends, it indicates that the left boundary has been found and is where the mid refers
      result[0] = mid;
      // After the left boundary is found, recover the mid pointer, reinitialize the left and right pointers, and start searching for the right boundary
      // Here is the part of [step 3]
      mid = Math.floor((back - front) / 2 + front);
      left = mid;
      right = back;
      while(!(nums[mid] === target && nums[mid + 1] !== target)) {
        // [tag2] note that Math.ceil should be used here instead of Math.floor. Please see [Supplement 1] below for details
        mid = Math.ceil((right - left) / 2 + left);
        left = nums[mid] === target ? mid : left;
        right = nums[mid] === target ? right : mid;
      }
      // After the while loop above ends, it indicates that the right boundary has been found and is where the mid refers
      result[1] = mid;
      // After finding the left and right boundaries, the outermost while loop should also stop, so you should break directly here
      break;
    }
    // Here is the part of [step 1]
    front = nums[mid] < target ? mid + 1 : front;
    back = nums[mid] < target ? back : mid - 1;
  }
  return result;
};


Submit records
88 / 88 Pass test cases
 Status: passed
 Execution time: 68 ms, At all JavaScript Defeated 81 in submission.37%User
 Memory consumption: 39.6 MB, At all JavaScript Defeated 22 in submission.12%User
 Time: 2021/10/02 19:39

It can be seen that the time performance of this method is better, but the spatial performance is not as good as the previous two solutions. After all, several auxiliary variables are used. And this writing is more complex in understanding.

[Supplement 1]
Math.floor is used in [tag1] to deal with the coincidence of mid pointer and right pointer. If Math.ceil is used, it will fall into an endless loop in step [2]. (for example, Num = [8,8,9])

Similarly, Math.ceil is used in [tag2] to deal with the coincidence of mid pointer and left pointer. If Math.floor is used, it will fall into an endless loop in step [3]. (for example, Num = [7,8,8])

The specific process can be observed in the developer tool of the browser.

Official explanation

Update: July 29, 2021 18:43:21

Because I consider the ownership of copyright, I won't paste the specific code in the [official solution], which can be viewed in the link below.

Update: October 2, 2021 20:42:41

reference resources: Find first and last position of element in sort array - find first and last position of element in sort array - LeetCode

[end of update]

Relevant reference

Update: October 2, 2021 20:41:24
reference resources: [algorithm LeetCode] 704. Binary search_ Lai nianan's blog - CSDN blog

Tags: Javascript Algorithm leetcode array

Posted on Thu, 07 Oct 2021 19:04:43 -0400 by *mt