023-在排序数组中查找元素的第一个和最后一个位置 (Find First and Last Position of Element in Sorted Array)
LeetCode 第23题 - 在排序数组中查找元素的第一个和最后一个位置的详细题解,包含二分查找算法和JavaScript实现
题目描述
给你一个按照非递减顺序排列的整数数组 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]示例 3:
输入: nums = [], target = 0
输出: [-1,-1]示例 4:
输入: nums = [1], target = 1
输出: [0,0]示例 5:
输入: nums = [2,2], target = 2
输出: [0,1]解题思路
二分查找法(推荐)
由于数组是有序的,我们可以使用二分查找来找到目标值的第一个和最后一个位置。
核心思想:
- 使用两次二分查找,分别找到目标值的左边界和右边界
- 左边界:找到第一个等于目标值的位置
- 右边界:找到最后一个等于目标值的位置
- 如果左边界为-1,说明目标值不存在
算法步骤:
- 实现查找左边界的二分查找函数
- 实现查找右边界的二分查找函数
- 分别调用两个函数找到左右边界
- 返回结果数组
JavaScript 解决方案
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
const searchRange = function(nums, target) {
// 查找左边界
const findLeft = (nums, target) => {
let left = 0;
let right = nums.length - 1;
let result = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) {
result = mid;
right = mid - 1; // 继续向左查找
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
};
// 查找右边界
const findRight = (nums, target) => {
let left = 0;
let right = nums.length - 1;
let result = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) {
result = mid;
left = mid + 1; // 继续向右查找
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
};
const left = findLeft(nums, target);
const right = findRight(nums, target);
return [left, right];
};复杂度分析
时间复杂度:O(log n)
- 使用两次二分查找,每次的时间复杂度为 O(log n)
- 总体时间复杂度为 O(log n)
空间复杂度:O(1)
- 只使用了常数额外空间
测试用例
// 测试函数
function testSearchRange() {
const testCases = [
{ nums: [5,7,7,8,8,10], target: 8, expected: [3,4] },
{ nums: [5,7,7,8,8,10], target: 6, expected: [-1,-1] },
{ nums: [], target: 0, expected: [-1,-1] },
{ nums: [1], target: 1, expected: [0,0] },
{ nums: [2,2], target: 2, expected: [0,1] },
{ nums: [1,2,3,4,5], target: 3, expected: [2,2] },
{ nums: [1,1,1,1,1], target: 1, expected: [0,4] },
{ nums: [1,2,3,4,5], target: 6, expected: [-1,-1] },
{ nums: [1,2,3,4,5], target: 0, expected: [-1,-1] },
{ nums: [1,1,2,2,3,3], target: 2, expected: [2,3] },
{ nums: [1,1,2,2,3,3], target: 1, expected: [0,1] },
{ nums: [1,1,2,2,3,3], target: 3, expected: [4,5] }
];
testCases.forEach((testCase, index) => {
const result = searchRange(testCase.nums, testCase.target);
const passed = JSON.stringify(result) === JSON.stringify(testCase.expected);
console.log(`测试用例 ${index + 1}: ${passed ? '✅' : '❌'}`);
console.log(`输入: nums = [${testCase.nums}], target = ${testCase.target}`);
console.log(`期望: [${testCase.expected}]`);
console.log(`实际: [${result}]`);
console.log('---');
});
}
// 运行测试
testSearchRange();关键点总结
- 左边界查找:找到目标值后继续向左查找,直到找到第一个位置
- 右边界查找:找到目标值后继续向右查找,直到找到最后一个位置
- 边界处理:注意数组为空和目标值不存在的情况
- 二分查找:利用数组的有序性进行高效的二分查找
- 结果组合:将左右边界组合成结果数组
总结
这道题是二分查找的经典应用,考察了对二分查找算法的理解和边界处理能力。
考察重点:
- 二分查找算法的应用
- 边界条件的处理
- 数组有序性的利用
- 代码的简洁性和可读性
- 算法的时间复杂度分析
这道题是很多面试中的常见题目,掌握二分查找的技巧对于解决类似问题很有帮助。在实际面试中,建议先分析问题,然后设计二分查找算法。