难度标识:
⭐:简单,⭐⭐:中等,⭐⭐⭐:困难。
tips:这里的难度不是根据LeetCode难度定义的,而是根据我解题之后体验到题目的复杂度定义的。
1.搜索插入位置 ⭐
思路
解这题的思路就是使用二分查找不断的缩小查找范围。具体做法就是拿左右两个指针去缩小查找范围。
-
初始化两个指针:一个在数组的开始位置(
left),另一个在数组的结束位置(right)。 -
循环直到
left超过right结束。 -
在每次迭代中,计算中间索引
mid = (left + right) / 2。 -
比较中间元素与目标值:
- 如果中间元素等于目标值,那么你已经找到了目标并且可以返回
mid。 - 如果目标值大于中间元素,这意味着目标值应该在数组的右半部分,因此将
left设置为mid + 1。 - 否则,目标值应该在数组的左半部分,所以将
right设置为mid - 1。
- 如果中间元素等于目标值,那么你已经找到了目标并且可以返回
-
当
left超过right时,跳出循环。此时,left指针会指向目标值应该插入的位置。
返回left作为答案。
这种方法的时间复杂度是O(log n),因为你在每次迭代中都将搜索空间减少了一半。
代码
var searchInsert = function (nums, target) {
let left = 0, right = nums.length - 1
while (left <= right) {
const mid = Math.floor((left + right) / 2)
if (nums[mid] === target) {
return mid
} else if (nums[mid] < target) {
left = mid + 1
} else {
right = mid - 1
}
}
return left
};
2. 搜索二维矩阵 ⭐
这题跟 LeetCode热题100矩阵题解析里的第4题搜索二维矩阵 II解法一模一样,II都能解决,那更别说I了,这题其实很简单。
3.在排序数组中查找元素的第一个和最后一个位置 ⭐
思路
这题我们可以使用独立的两次二分查找,分别找到要查找元素的左起点和右起点。
- 第一次查找目标值的开始位置。
- 第二次查找目标值的结束位置。
以下是核心思路:
-
查找目标值的开始位置:
- 使用标准的二分查找,但当找到目标值时,不要立即返回。
- 而是继续在左侧搜索(即,使
right指针移动到mid - 1),直到确定左侧没有相同的目标值为止。 - 最后,
left指针将指向目标值的开始位置。
-
查找目标值的结束位置:
- 再次使用二分查找,但这次当找到目标值时,你要继续在右侧搜索(即,使
left指针移动到mid + 1)。 - 直到确定右侧没有相同的目标值为止。
- 最后,
right指针将指向目标值的结束位置。
- 再次使用二分查找,但这次当找到目标值时,你要继续在右侧搜索(即,使
-
如果在上述两个步骤中,你没有找到目标值,那么应该返回
[-1, -1]。
这种方法的时间复杂度为O(log n)。因为我们只是进行了两次标准的二分查找。
代码
var searchRange = function (nums, target) {
function findLeft() {
let left = 0, right = nums.length - 1
while (left <= right) {
const mid = Math.floor((left + right) / 2)
if (nums[mid] < target) {
left = mid + 1
} else {
right = mid - 1
}
}
return left
}
function findRight() {
let left = 0, right = nums.length - 1
while (left <= right) {
const mid = Math.floor((left + right) / 2)
if (nums[mid] <= target) {
left = mid + 1
} else {
right = mid - 1
}
}
return right
}
let leftIndex = findLeft(), rightIndex = findRight()
if (leftIndex <= rightIndex) {
return [leftIndex, rightIndex]
}
return [-1, -1]
};
4.搜索旋转排序数组 ⭐
思路
要在时间复杂度为 O(logn) 的条件下解决这个问题,我们可以使用二分查找。但由于数组被旋转了,所以我们不能直接应用传统的二分查找。我们需要稍微修改策略。
核心思路如下:
-
确定中点和旋转的部分:首先,找到中点。这可以通过
(left + right) / 2来确定。接着,我们要确定哪部分是有序的。根据旋转的特点,至少有一半是有序的。 -
使用有序的部分:当我们确定了哪部分是有序的,我们可以检查目标值
target是否在这个有序的范围内。如果是,我们可以在这个范围内进行二分查找;如果不是,那么我们可以在另一半中查找。 -
递归或迭代:根据上述逻辑,我们可以递归地进行查找,或者使用一个循环来迭代地进行查找。
代码
var search = function (nums, target) {
let left = 0, right = nums.length - 1
while (left <= right) {
const mid = Math.floor((left + right) / 2)
if (nums[mid] === target) {
return mid
}
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1
} else {
left = mid + 1
}
} else {
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1
} else {
right = mid - 1
}
}
}
return -1
};
5.寻找旋转排序数组中的最小值 ⭐
思路
解这个问题的答案与旋转数组的特性有关。首先,我们知道原始数组是升序排列的。当我们对数组进行旋转时,其实就是将数组前面的一段移到后面去。
-
如果一个旋转数组的中间元素比最右侧元素大,这意味着中间元素是来自原始数组的左边部分(也就是较大的部分),而最右侧的元素来自原始数组的右边部分(也就是较小的部分)。所以,这种情况下的旋转点(也就是最小元素的位置)一定在中间元素的右侧。
例如,考虑数组
[4,5,6,7,0,1,2]。如果我们选择中间的元素7,它比最右侧的元素2要大,说明最小元素0位于7的右侧。 -
如果中间元素比最右侧元素小或等于,这意味着中间元素来自原始数组的右边部分(也就是较小的部分)。这时,旋转点可能就在中间元素的位置,或者在它的左侧。
根据上述原理,我们可以确定在哪一半继续进行搜索。
代码
var findMin = function (nums) {
let left = 0, right = nums.length - 1;
let minVal = 0
while (left < right) {
const mid = Math.floor((left + right) / 2)
if (nums[mid] > nums[right]) {
left = mid + 1
} else {
right = mid
}
}
return nums[left]
};
6. 寻找两个正序数组的中位数 ⭐⭐⭐
思路
解这题也是使用二分查找的方法。但是这题说实话还是有点难的,我做了那么多题,我觉得这题比前面的题都难一些,题解视频我看了几遍才明白,简单来说就相当于使用一个分割线去给数组进行大数小数分组,从而找到中位数的位置,这题得看视频理解学习,不懂的话就多看几遍。
代码
var findMedianSortedArrays = function (nums1, nums2) {
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1]
}
let m = nums1.length, n = nums2.length;
let halfLen = Math.floor((m + n + 1) / 2)
let left = 0, right = m
while (left <= right) {
let mid = Math.floor((left + right) / 2)
let j = halfLen - mid
let num1Left = mid === 0 ? -Infinity : nums1[mid - 1]
let num1Right = mid === m ? Infinity : nums1[mid]
let num2Left = j === 0 ? -Infinity : nums2[j - 1]
let num2Right = j === n ? Infinity : nums2[j]
if (num1Left <= num2Right && num2Left <= num1Right) {
if ((m + n) % 2 === 1) {
return Math.max(num1Left, num2Left)
} else {
return (Math.max(num1Left, num2Left) + Math.min(num1Right, num2Right)) / 2
}
}
if (num1Left > num2Right) {
right = mid - 1
} else {
left = mid + 1
}
}
};
总体来说,二分查找的题目除了上面的最后一题需要多看几遍视频理解一些,其他的还是比较简单的。