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

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

链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/

题目描述见链接内容。

解法1:暴力法

逐个匹配数组元素,用一个标识符来标识第一个匹配的位置和最后一个匹配的位置:

var searchRange = function (nums, target) {
  let result = [-1, -1],
    flag = false;
  for (let i = 0, len = nums.length; i < len; i++) {
    if (nums[i] === target) {
      if (!flag) {
        result[0] = i;
        flag = true;
      }
      result[1] = i;
    } else {
      if (flag) {
        result[1] = i - 1;
        return result;
      }
    }
  }
  return result;
};
  • 时间复杂度:${O(N)}$
  • 空间复杂度:${O(1)}$
  • 执行结果:执行用时64ms,在所有JavaScript提交中击败了97%的用户,内存消耗:39.3MB,在所有JavaScript提交中击败了46%的用户

解法2:伪·二分法

因为是有序数组,并且需要查找元素,自然可以想到二分法,但是我这个是一个伪二分法,指的是利用二分法不全面,无法做到${O(log N)}$的时间复杂度。

我的思路是,利用二分法找到一个等于target的下标,然后从这个下标开始,向左右扩展,直到左右元素与target不等或者越界

var searchRange = function (nums, target) {
  let result = [-1, -1],
    left = 0,
    right = nums.length,
    base = -1;

  // 使用二分法找到与 target 相等的下标
  while (left <= right) {
    const mid = ~~((left + right) / 2);
    if (nums[mid] === target) {
      base = mid;
      break;
    } else {
      if (nums[mid] < target) {
        left = mid + 1;
      } else {
        right = mid - 1;
      }
    }
  }

  // 如果找到的话,向左右扩展
  if (base > -1) {
    result = [base, base];
    let start = base - 1,
      end = base + 1;
    while (start >= 0 || end <= nums.length - 1) {
      if (nums[start] === nums[base]) {
        result[0] = start;
        start -= 1;
      } else {
        start = -1;
      }
      if (nums[end] === nums[base]) {
        result[1] = end;
        end += 1;
      } else {
        end = nums.length;
      }
    }
  }

  return result;
};
  • 时间复杂度:${O(N * logN)}$,最坏的情况下在二分查找之后还要再完整遍历一遍数组
  • 空间复杂度:${O(1)}$
  • 执行结果:执行用时72ms,在所有JavaScript提交中击败了86%的用户,内存消耗:39.2MB,在所有JavaScript提交中击败了66%的用户

解法3:真·二分法

实际上,第一个位置就是第一个大于等于target的位置,第二个位置就是第一个大于target的位置减一,直接利用二分法查找两边即可

用习惯的二分法套路:

function binarySearch(nums, target, equal) {
  let left = 0,
    right = nums.length;

  while (left < right) {
    const mid = ~~((left + right) / 2);
    if (nums[mid] < target || (equal && nums[mid] <= target)) {
      left = mid + 1;
    } else {
      right = mid;
    }
  }
  return left;
}

要注意的:

  1. 循环终止的条件不包含等号,这样结束循环时,leftright是相等的,任意返回一个就可以
  2. if中的逻辑判断条件,是与我们的目的相反的,要找到第一个大于等于target的位置,if中就取反,找小于target的下标
  3. 因为mid是向下取整,所以left迭代时需要+1,而right向下取则不需要-1
  4. 利用equal来复用代码,equaltrue时,用来寻找第一个大于target的位置

完整代码:

function binarySearch(nums, target, equal) {
  let left = 0,
    right = nums.length;

  while (left < right) {
    const mid = ~~((left + right) / 2);
    if (nums[mid] < target || (equal && nums[mid] <= target)) {
      left = mid + 1;
    } else {
      right = mid;
    }
  }
  return left;
}

var searchRange = function (nums, target) {
  // 找到第一个大于等于 target 的数字
  const start = binarySearch(nums, target),
    // 找到第一个大于 target 的数字
    end = binarySearch(nums, target, true) - 1;
  if (start > -1 && end < nums.length && nums[start] === target && nums[start] === nums[end]) {
    return [start, end];
  }
  return [-1, -1];
};
  • 时间复杂度:${O(logN)}$
  • 空间复杂度:${O(1)}$
  • 执行结果:执行用时72ms,在所有JavaScript提交中击败了86%的用户,内存消耗:39.3MB,在所有JavaScript提交中击败了46%的用户
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值