704. 二分查找
关键点
- java数组长度:可以通过nums.length直接获取,而不会像cpp一样麻烦。
- while循环终止条件:这里我采用
left <= right
的方式。
假设最后只剩两个元素,如果不加等号的条件判断,则会直接忽略掉left或者right这个位置的元素,导致结果出错,所以采用小于等于的方式是正确的。
这里也就是卡尔哥说的左闭右闭的情况,此时的左右两个元素都参与比较,当比较到最后时就是从这两个元素中的一个出来和target进行比较,因此也是此类问题需要加等号的原因。
针对左闭右开的情况,一个指针所指向的元素不可能是“既取又不取”的情况,所以此时while中没必要加等号再进行左和右的比较。 - 中值mid的计算方法:如果直接采用
(left+right) / 2
得到中值,那么假设left和right都为2^31 - 1
(java中int类型的最大值),两者进行相加直接超过了int所能承受的最大数值,会导致程序出错。因此常规的整数相加一般采用left + (right - left)
的形式,让减法先行。
34. 在排序数组中查找元素的第一个和最后一个位置
此题主要考查二分查找的运用,这里我直接将二分查找封装成一个一个函数,通过指定开始和结束的数组下标来限定二分查找的位置。然后通过在while循环中不断地缩小二分查找的范围,直到触碰到范围边界或者数组边界为止。这里针对数组边界,在while前后都加了if条件限制,防止特殊情况的出现。
class Solution {
public int[] searchRange(int[] nums, int target) {
int mid = binarySearch(nums, target, 0, nums.length - 1); //初始二分查找,确定数组内是否有该元素
int[] result = {-1, -1};
if(mid == -1){
return result;
}else{
//以初始结果为准,划分左右两片区域,再进行二分查找,找出两片区域对应的mid
int left_mid = binarySearch(nums, target, 0, mid);
int right_mid = binarySearch(nums, target, mid, nums.length - 1);
if(left_mid > 0){//此处为数组边界的限制条件
while(nums[left_mid - 1] == target){
left_mid = binarySearch(nums, target, 0, left_mid);//在左侧区域循环进行二分查找
if(left_mid <= 0){
break;//此处为数组边界的限制条件
}
}
}
//右侧区域同左侧区域
if(right_mid < nums.length - 1){
while( nums[right_mid + 1] == target){
System.out.println(right_mid);
right_mid = binarySearch(nums, target, right_mid + 1, nums.length - 1);
System.out.println(right_mid);
if(right_mid >= nums.length - 1){
break;
}
}
}
//记录返回的数组范围下标
result[0] = left_mid;
result[1] = right_mid;
}
return result;
}
//封装二分查找算法
public int binarySearch(int[] nums, int target, int start, int end){
int left = start;
int right = end;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid - 1;
}else if(nums[mid] < target){
left = mid + 1;
}
}
return -1;
}
}
27. 移除元素
这里先贴一段自己的代码:
class Solution {
public int removeElement(int[] nums, int val) {
int j = 0; //计数器
for(int i = 0; i < nums.length; i++){ //一次循环遍历所有元素
if(nums[i] == val){
j++; //遇到目标值,计数器加一
}else{
if(j > 0){ //只有当存在目标值时才进行移动
nums[i - j] = nums[i]; //将此时遍历到的元素,向前移动i - j位
}
}
}
return nums.length - j;
}
}
可以看到我的代码是比较常规的写法,没有使用费劲的双重for循环,也没有使用双指针法,不过也算是双指针法的一个变体。我的方法通过计数器来判断此时遍历的元素需要向前移动的次数,而双指针法则是通过前一个指针记录当前遍历的元素需要移动到的位置。两种方法有异曲同工之处。