704. 二分查找
给定一个
n
个元素有序的(升序)整型数组nums
和一个目标值target
,写一个函数搜索nums
中的target
,如果目标值存在返回下标,否则返回-1
工作后太久没刷题,二分法逻辑实现上感觉很简单,但实际写起来,因为各种边界条件问题,也是调试了很多遍,最后虽然过了,但总感觉糊里糊涂的。
思路梳理
二分法主要使用场景为在不重复的有序数中查找特定数
二分法原理已有很多文章做过说明在,这里不再赘述。现着重说下边界条件的问题。
取左右端点分别为left,right,中点值mid = (left + right)/ 2。
根据循环终止条件nums[ left] < nums [right] 或是 nums[ left] <= nums [right],有两种写法
写法一
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
写法一中,可以将left和right包含的数组段看作一个左右封闭的数组,即[left, right],包含两端的端点,所以当left==right时,[left,right]存在意义,表示一个点,while中可以写等号。每次缩小范围时,由于mid已经在上一次判断中被比较过,没必要再次重复比较,所以取左右端点时,应该在mid的基础上左右加减1.
写法二
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid;
}
写法二中,可以将左右端点包含的数组看作一个左闭右开区间,即[left,right),包含左端点,不含右端点,当left==right,[left,right)数组无意义,所以while内不能取等号。同时,每次取子数组时,由于数组并不包含右端点,所以right的赋值应该直接取mid,如果取mid - 1,则会有值覆盖不到。
拓展题目:
- 35.搜索插入位置(opens new window)
- 34.在排序数组中查找元素的第一个和最后一个位置(opens new window)
- 69.x 的平方根
- 367.有效的完全平方数
以上题目皆可使用二分查找法完成,这里补充说明一个知识点:
当二分查找未找到值退出时,左端点会指向大于target的最小值,右端点会指向小于target的最大值。
27. 移除元素
给你一个数组
nums
和一个值val
,你需要 原地 移除所有数值等于val
的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用
O(1)
额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
一般涉及到数组的移动,会考虑使用双指针法,一个指针指向当前遍历位置,一个指针定位可移入位置。下面看代码
我自己提交通过代码
public int removeElement(int[] nums, int val) {
int len = nums.length;
int ofset = 0;
for (int i = 0; i < nums.length; i++){
while (nums[i] == val ){
len--;
i++;
ofset ++;
if(i >= nums.length) return len;
}
if(ofset > 0){
nums[i - ofset] = nums[i];
}
}
return len;
}
同样时双指针思路,只不过有一个指针是通过ofset偏移值来指定,但是这种写法可读性差,容易出bug,复用性不高。
比较好的写法
public int removeElement(int[] nums, int val) {
// 快慢指针
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] != val) {
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}