二分查找 (Binary Search)
二分查找是一种高效的搜索算法,时间复杂度为 O(log n),适用于有序数组。
可以通过这个Binary and Linear Search Visualization感受一下二分查找,更易于理解

问题分析
1、给定一个升序排列的整数数组 nums 和一个目标值 target
2、需要找到 target 在数组中的索引位置,如果不存在则返回 -1
3、必须使用 O(log n) 时间复杂度的算法
解题思路
-
初始化指针:设置两个指针
left和right分别指向数组的开始和结束 -
循环查找:当
left <= right时:-
计算中间索引
mid -
如果
nums[mid] == target,直接返回mid -
如果
nums[mid] < target,说明目标在右半部分,调整left -
如果
nums[mid] > target,说明目标在左半部分,调整right
-
-
未找到:如果循环结束仍未找到,返回 -1
Java 实现代码
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出,等同于 (left + right)/2
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
}
关键点解释
-
防止整数溢出:
-
计算
mid时使用left + (right - left) / 2而不是(left + right) / 2 -
这样可以避免当
left和right都很大时相加导致的整数溢出
-
-
循环条件:
-
使用
left <= right而不是<,这样可以确保当left == right时也能检查最后一个元素
-
-
边界调整:
-
当
nums[mid] < target时,调整left = mid + 1(因为mid已经检查过) -
当
nums[mid] > target时,调整right = mid - 1(同理)
-
复杂度分析
1、时间复杂度:O(log n),每次都将搜索范围减半
2、空间复杂度:O(1),只使用了常数级别的额外空间
测试用例验证
public static void main(String[] args) {
Solution solution = new Solution();
// 示例1
int[] nums1 = {-1, 0, 3, 5, 9, 12};
System.out.println(solution.search(nums1, 9)); // 输出: 4
// 示例2
System.out.println(solution.search(nums1, 2)); // 输出: -1
// 边界测试
int[] nums2 = {5};
System.out.println(solution.search(nums2, 5)); // 输出: 0
System.out.println(solution.search(nums2, 1)); // 输出: -1
}
通过这个解法,可以高效地解决有序数组的查找问题。记住二分查找的前提是数组必须是有序的,如果无序需要先排序。
移除元素

题目要求我们原地移除数组中所有等于给定值 val 的元素,并返回剩余元素的数量 k。移除后的数组前 k 个元素应为不等于 val 的元素,其余元素可以忽略。
方法思路
双指针法
-
初始化指针:使用两个指针,
i用于遍历数组,k用于记录不等于val的元素的位置。 -
遍历数组:遍历数组,当遇到不等于
val的元素时,将其放到k的位置,并递增k。 -
返回结果:最后
k即为剩余元素的数量。
Java代码
class Solution {
public int removeElement(int[] nums, int val) {
int k = 0; // 指针,记录不等于val的元素的位置
for (int i = 0; i < nums.length; i++) {
if (nums[i] != val) {
nums[k] = nums[i];
k++;
}
}
return k;
}
}
代码解释
-
初始化指针
k:k从0开始,表示下一个不等于val的元素应该放置的位置。 -
遍历数组:使用
i遍历数组的每个元素。如果当前元素nums[i]不等于val,则将其复制到nums[k]的位置,并递增k。 -
返回结果:遍历完成后,
k的值即为剩余元素的数量,数组的前k个元素即为所有不等于val的元素。
示例验证
示例1
输入:nums = [3,2,2,3], val = 3
执行过程:
i=0:nums[0]=3(等于val),跳过。
i=1:nums[1]=2(不等于val),nums[0]=2,k=1。
i=2:nums[2]=2(不等于val),nums[1]=2,k=2。
i=3:nums[3]=3(等于val),跳过。
输出:k=2,数组前2个元素为 [2,2]。
示例2
输入:nums = [0,1,2,2,3,0,4,2], val = 2
执行过程:
i=0:nums[0]=0(不等于val),nums[0]=0,k=1。
i=1:nums[1]=1(不等于val),nums[1]=1,k=2。
i=2:nums[2]=2(等于val),跳过。i=3:nums[3]=2(等于val),跳过。
i=4:nums[4]=3(不等于val),nums[2]=3,k=3。
i=5:nums[5]=0(不等于val),nums[3]=0,k=4。
i=6:nums[6]=4(不等于val),nums[4]=4,k=5。
i=7:nums[7]=2(等于val),跳过。
输出:k=5,数组前5个元素为 [0,1,3,0,4]。
复杂度分析
时间复杂度:O(n),其中 n 是数组的长度。我们只需遍历一次数组。
空间复杂度:O(1),没有使用额外的存储空间,是原地操作。
其他方法
双指针优化(当要移除的元素很少时)
如果 val 在数组中出现的次数很少,可以将等于 val 的元素与数组末尾的元素交换,并减少数组长度。这样可以减少不必要的复制操作。
class Solution {
public int removeElement(int[] nums, int val) {
int i = 0;
int n = nums.length;
while (i < n) {
if (nums[i] == val) {
nums[i] = nums[n - 1];
n--;
} else {
i++;
}
}
return n;
}
}
方法思路
-
初始化指针:
i从0开始,n为数组长度。 -
遍历数组:当
nums[i]等于val时,将nums[i]与nums[n-1]交换,并减少n。否则,递增i。 -
返回结果:
n即为剩余元素的数量。
适用场景
当 val 在数组中出现的次数较少时,此方法可以减少元素的移动次数,提高效率。
总结
本题的关键在于使用双指针技巧,高效地原地修改数组。第一种方法适用于大多数情况,第二种方法在特定情况下(val 出现较少时)更优。理解双指针的思想对解决类似问题非常有帮助。
有序数组的平方

问题理解
题目给定一个非递减顺序排列的整数数组 nums,要求返回每个元素平方后按非递减顺序排列的新数组。
方法思路
由于数组可能包含负数,平方后的数组可能会打乱原有的顺序。我们可以利用双指针法,从数组的两端向中间遍历,比较两端的平方值,将较大的平方值放入结果数组的末尾。
步骤:
-
初始化指针:
left指向数组起始位置,right指向数组末尾。 -
比较平方值:比较
nums[left]和nums[right]的平方值,将较大的平方值放入结果数组的当前末尾位置。 -
移动指针:根据比较结果移动
left或right指针。 -
填充结果数组:重复上述步骤直到所有元素处理完毕。
解决代码
class Solution {
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int[] result = new int[n];
int left = 0, right = n - 1;
int index = n - 1; // 从后往前填充结果数组
while (left <= right) {
int leftSquare = nums[left] * nums[left];
int rightSquare = nums[right] * nums[right];
if (leftSquare > rightSquare) {
result[index] = leftSquare;
left++;
} else {
result[index] = rightSquare;
right--;
}
index--;
}
return result;
}
}
代码解释
-
初始化:创建结果数组
result,初始化双指针left和right,以及填充索引index。 -
遍历比较:每次比较
left和right指针所指元素的平方值,将较大的值放入result的当前index位置。 -
移动指针:根据比较结果移动指针,
left右移或right左移,index递减。 -
返回结果:最终
result数组即为按非递减顺序排列的平方值数组。
示例验证
示例1
输入:nums = [-4,-1,0,3,10]
执行过程:
left=0,right=4:比较 16 和 100,100 较大,放入 result[4],right--。
left=0,right=3:比较 16 和 9,16 较大,放入 result[3],left++。
left=1,right=3:比较 1 和 9,9 较大,放入 result[2],right--。
left=1,right=2:比较 1 和 0,1 较大,放入 result[1],left++。
left=2,right=2:比较 0 和 0,放入 result[0]。输出:[0,1,9,16,100]。
示例2
输入:nums = [-7,-3,2,3,11]
执行过程:
left=0,right=4:比较 49 和 121,121 较大,放入 result[4],right--。
left=0,right=3:比较 49 和 9,49 较大,放入 result[3],left++。
left=1,right=3:比较 9 和 9,放入 result[2],right--。
left=1,right=2:比较 9 和 4,9 较大,放入 result[1],left++。
left=2,right=2:比较 4 和 4,放入 result[0]。
输出:[4,9,9,49,121]。
复杂度分析
时间复杂度:O(n),只需一次遍历数组。
空间复杂度:O(n),需要额外的数组存储结果。
其他方法
直接排序法
-
平方后排序:先计算每个元素的平方,然后对结果数组进行排序。
-
代码:
class Solution { public int[] sortedSquares(int[] nums) { for (int i = 0; i < nums.length; i++) { nums[i] = nums[i] * nums[i]; } Arrays.sort(nums); return nums; } } -
复杂度:
-
时间复杂度:O(n log n),排序步骤占主导。
-
空间复杂度:O(1)(原地排序)或 O(n)(额外空间取决于排序算法)。
-
总结
双指针法利用了原数组有序的特性,避免了排序的开销,是最优解法。直接排序法虽然简单,但在效率上不如双指针法。理解双指针的思想对解决类似问题非常有帮助。
1082

被折叠的 条评论
为什么被折叠?



