1. 题目
https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/
考察点
这道题主要考察的是数组和二分查找的知识点。😊
- 数组是一种基本的数据结构,可以用来存储多个相同类型的数据,可以通过下标访问任意元素,具有随机访问的特点。
- 二分查找是一种高效的查找算法,可以在有序的数组中快速找到目标值或者其位置,每次查找都会将查找范围缩小一半,直到找到或者不存在为止。
- 二分查找有多种变体,例如找到目标值的左边界或右边界,或者找到第一个大于等于或小于等于目标值的元素等。需要注意边界条件和循环条件的选择。
2. 解法
三种解法
这道题有以下几种解法:
- 二分查找法:使用两个辅助函数,分别用于找到目标值的左边界和右边界。这种方法的时间复杂度是O(log n),空间复杂度是O(1)。
- 线性扫描法:从左到右遍历数组,找到第一个等于目标值的元素,记录其位置为左边界;从右到左遍历数组,找到第一个等于目标值的元素,记录其位置为右边界。这种方法的时间复杂度是O(n),空间复杂度是O(1)。
- 双指针法:使用两个指针low和high,分别指向数组的首尾。如果low指向的元素小于目标值,就将low右移;如果high指向的元素大于目标值,就将high左移;如果low和high都指向目标值,就记录其位置为左边界和右边界。这种方法的时间复杂度是O(n),空间复杂度是O(1)。
这三种解法的对比:
- 二分查找法:这种方法利用了数组已经排序的特点,通过不断缩小查找范围,找到目标值的左边界和右边界。这种方法的时间复杂度是O(log n),空间复杂度是O(1)。这种方法的优点是效率高,缺点是实现较复杂,需要注意边界条件。
- 线性扫描法:这种方法直接遍历数组,找到第一个等于目标值的元素和最后一个等于目标值的元素,作为左边界和右边界。这种方法的时间复杂度是O(n),空间复杂度是O(1)。这种方法的优点是简单直观,缺点是效率低,可能需要遍历整个数组。
- 双指针法:这种方法使用两个指针,分别从数组的首尾向中间移动,跳过不等于目标值的元素,直到找到左边界和右边界。这种方法的时间复杂度是O(n),空间复杂度是O(1)。这种方法的优点是实现简单,缺点是效率低,可能需要遍历整个数组。
综上所述,如果要求时间复杂度最低,可以选择二分查找法;如果要求实现简单,可以选择线性扫描法或双指针法。😊
解法一:二分查找法
思路
这道题的主要思路是使用二分查找法来找到目标值在数组中的第一个和最后一个位置。具体步骤如下:
- 定义两个辅助函数,分别用于找到目标值的左边界和右边界。
- 在左边界函数中,如果中间元素等于目标值,那么向左继续查找,否则向右查找。
- 在右边界函数中,如果中间元素等于目标值,那么向右继续查找,否则向左查找。
- 在主函数中,先调用左边界函数,如果返回值不等于-1,说明目标值存在,再调用右边界函数,得到最终的结果。
- 如果左边界函数返回-1,说明目标值不存在,直接返回[-1, -1]。
具体实现
class Solution { public int[] searchRange(int[] nums, int target) { int[] range = new int[2]; range[0] = findLeftBound(nums, target); // 调用左边界函数 if (range[0] == -1) { // 如果返回-1,说明目标值不存在 return new int[]{-1, -1}; } range[1] = findRightBound(nums, target); // 调用右边界函数 return range; } // 左边界函数 private int findLeftBound(int[] nums, int target) { int left = 0; int right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid - 1; // 向左继续查找 } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } if (left >= nums.length || nums[left] != target) { // 检查是否越界或者没有找到 return -1; } return left; // 返回左边界 } // 右边界函数 private int findRightBound(int[] nums, int target) { int left = 0; int right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { left = mid + 1; // 向右继续查找 } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } if (right < 0 || nums[right] != target) { // 检查是否越界或者没有找到 return -1; } return right; // 返回右边界 } }
解法二:线性扫描法
思路
- 首先,初始化一个结果数组,用-1填充。
- 然后,从左到右遍历数组,找到第一个等于目标值的元素,记录其位置为结果数组的第一个元素,即左边界。
- 接着,从右到左遍历数组,找到第一个等于目标值的元素,记录其位置为结果数组的第二个元素,即右边界。
- 最后,返回结果数组。
具体实现
class Solution { public int[] searchRange(int[] nums, int target) { int[] range = new int[2]; range[0] = -1; range[1] = -1; // 从左到右遍历数组,找到第一个等于目标值的元素,记录其位置为左边界 for (int i = 0; i < nums.length; i++) { if (nums[i] == target) { range[0] = i; break; } } // 如果左边界为-1,说明目标值不存在,直接返回[-1, -1] if (range[0] == -1) { return range; } // 从右到左遍历数组,找到第一个等于目标值的元素,记录其位置为右边界 for (int i = nums.length - 1; i >= 0; i--) { if (nums[i] == target) { range[1] = i; break; } } return range; } }
解法三:双指针法
思路
代码的思想是这样的:
- 首先,初始化两个指针low和high,分别指向数组的首尾。
- 然后,进入一个循环,直到low和high相遇或者跨越。
- 在循环中,判断low指向的元素是否小于目标值,如果是,就将low右移一位,表示跳过这个元素;判断high指向的元素是否大于目标值,如果是,就将high左移一位,表示跳过这个元素。
- 如果low和high都指向目标值,就说明找到了目标值的左边界和右边界,跳出循环。
- 最后,检查low和high是否都指向目标值,如果是,就将它们的位置赋值给结果数组;如果不是,就说明目标值不存在,返回[-1, -1]。
具体实现
class Solution { public int[] searchRange(int[] nums, int target) { int[] range = new int[2]; int low = 0; int high = nums.length - 1; while (low < high) { if (nums[low] < target) { low++; } if (nums[high] > target) { high--; } if (nums[low] == target && nums[high] == target) { break; } } if (nums[low] == target && nums[high] == target) { range[0] = low; range[1] = high; } else { range[0] = -1; range[1] = -1; } return range; } }