[路飞]_在排序数组中查找元素的第一个和最后一个位置

108 阅读1分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

34. 在排序数组中查找元素的第一个和最后一个位置

题目

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例1

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例2

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

题解

常规枚举

使用两个变量left和right,当 nums[i] === target 时,将下标记录到left和right,随后更新right直到找到最后一个目标值。最后返回变量值即可;

时间复杂度:O(n)

var searchRange = function(nums, target) {
  let l = nums.length;
  if (l == 0) return [-1, -1];
  let left = -1;
  let right = -1;
  for(let i = 0 ; i < l ; i++){
      if(nums[i] === target){
          if(left === -1){
              left = i
              right = i
          }else{
              right = i
          }
      }
  }
  return [left,right]
 
};

二分法

数组升序排列,查找查找目标值,可以使用二分法查找,但是题目说目标值不唯一。这里需要在二分法中处理一下。

如果目标值唯一二分法套路

  • targettarget
  • left=0left = 0;
  • right=nums.lengthright = nums.length
  • 在区间[left,right][left,right]上查找targettarget
  • mid=(left+right)>>1mid = (left + right) >> 1
  • mid===targetmid === target 返回 midmid
  • mid>targetmid > target ,在区间[left,mid1][left,mid-1]查找
  • mid<targetmid < target ,在区间[mid+1,right][mid+1,right]查找

如果目标值不唯一二分法找左侧第一个目标数

  • targettarget
  • left=0left = 0;
  • right=nums.lengthright = nums.length
  • mid=(left+right)>>1mid = (left + right) >> 1
  • 这里需要处理: mid===targetmid === target,在区间[left,mid][left,mid]查找;这里找到目标值不确定是第一个目标值,所以需要在[left,mid][left,mid]继续查找
  • mid>targetmid > target ,在区间[left,mid][left,mid-]查找
  • mid<targetmid < target ,在区间[mid+1,right][mid+1,right]查找

如果目标值不唯一二分法找右侧第一个目标数

  • targettarget
  • left=0left = 0;
  • right=nums.lengthright = nums.length
  • mid=(left+right)>>1mid = (left + right) >> 1
  • 这里需要处理: mid===targetmid === target,在区间[mid,right][mid,right]查找;这里找到目标值不确定是右侧第一个目标值,同时也不确定目标值的下一个元素依然时目标值,所以需要在[left,mid][left,mid]继续查找
  • mid>targetmid > target ,在区间[left,mid][left,mid-]查找
  • mid<targetmid < target ,在区间[mid+1,right][mid+1,right]查找

二分法代码

var searchRange = function(nums, target) {
  let l = nums.length;
  if (l == 0) return [-1, -1];
 
  let leftVal = getLeft();
  if (leftVal == -1) return [-1, -1];
  let rightVal = getRight();
  return [leftVal, rightVal];
// 获取左侧目标值下标
  function getLeft() {
    let left = 0;
    let right = l - 1;
    while (left < right) {
      let mid = left + Math.floor((right - left) / 2);
      if (nums[mid] < target) {
        left = mid + 1;
      } else if (nums[mid] == target) {
        right = mid;
      } else if (nums[mid] > target) {
        right = mid;
      }
    }
    if (nums[left] != target) {
      return -1;
    }
    return left;
  }

// 获取右侧目标值下标
  function getRight() {
    let left = 0;
    let right = l - 1;
    while (left < right) {
      let mid = left + Math.ceil((right - left) / 2);
      //console.log("ds", left, right);
      if (nums[mid] < target) {
        left = mid + 1;
      } else if (nums[mid] == target) {
        left = mid;
      } else if (nums[mid] > target) {
        right = mid - 1;
      }
    }

    if (nums[left] != target) {
      return -1;
    }
    return left;
  }
};

上述代码中获取左侧目标值下标、获取右侧目标值下标代码冗余严重可以,但是与文字描述思路一致,便于理解,后面可以优化代码

优化后代码

var searchRange = function (nums, target) {
  let l = nums.length
  if (l == 0) return [-1, -1]

  let leftVal = getIndex(true)
  if (leftVal == -1) return [-1, -1]
  let rightVal = getIndex(false)
  return [leftVal, rightVal]

  function getIndex(sign) {
    let left = 0
    let right = l - 1
    let index = -1
    while (left <= right) {
      let mid = left + Math.floor((right - left) / 2)

      if (nums[mid] > target) {
        right = mid - 1
      } else if (nums[mid] < target) {
        left = mid + 1
      } else {
        index = mid

        if (sign) {
          right = mid - 1
        } else {
          left = mid + 1
        }
      }
    }

    return index
  }
}