算法学习day01:二分查找,数组移除元素(Java)
二分查找
简介
二分查找是比较常见的查找方式,大概逻辑是:把一个递增/递减的数组从中间分成两半,找到有目标元素的一半数组 继续分开,一直循环,直到只剩下目标元素。
想使用二分查找,前提条件是:数组是递增/递减的,且数组没有重复的元素。
写法
二分查找的写法主流上有两种:左闭右开,左闭右闭,具体的解释建议配合下面代码看。
需要定义的变量有:left,right,middle,数组num,目标target(后两者是已知条件)
左闭右闭
public static void main(String[] args) {
int[] num1 = {0,1,3,4,6,7,8,9,10,25,100};
//使用前提:数组递增或递减,数组内没有重复的数字
System.out.println(search01(num1, 6));
}
public static int search01(int[] num, int target){
int left = 0;
int right = num.length -1;
int middle;
while(left <= right){
middle = left + (right-left)/2;
if(target > num[middle]){
left = middle + 1;
}else if (target < num[middle]){
right = middle - 1;
}else return middle ;
}
return -1;
}
首先定义需要查找的左右区间。如数组{0,1,3,4,6,7,8,9,10,25,100}
左闭右闭相当于包含left和right,即类似数学中[1,3],所取的范围是包括1和3的
用这种思路,我们定义出left为0, righ为数组长度-1。left<=right时,开始二分查找
先定义middle,应该是(left+right)/2,但这样写在极端情况下容易出错,如left+right超过int最大值,所以建议使用middle = left + (right - left)/2的写法(int类型的数除以2会自动忽略小数,但问题不大)
用middle把数组分成两半之后,检查target在数组的左边还是右边
如果在左边的话,就更新右边的边界为middle-1,如果在右边就更新左边界为middle+1
注意:写成middle自然也没有太大问题,但会让你的边界出错。因为这是一个左闭右闭的区间,经过一次二分后,你已经知道middle不是target,所以要把target丢出去,这样做可以严格限制边界,思路清晰,也可以减少运行的时间。
以此类推,不停地更新左右边界,直到left = right,此时left和right划出的范围内只有一个元素,如果这个元素还不是target,说明这个数组里面没有目标元素,随着right或者left的更新(middle±1),跳出循环
如果找到了目标元素(middle = target),则返回middle。
左闭右开
public static void main(String[] args) {
int[] num1 = {0,1,3,4,6,7,8,9,10,25,100};
//使用前提:数组递增或递减,数组内没有重复的数字
System.out.println(search02(num1, 6));
}
public static int search02(int[] num, int target){
int left = 0;
int right = num.length ;
int middle;
while (left < right){
middle = left+(right-left)/2;
if(target < num[middle]){
right = middle;
}else if (target > num[middle]){
left = middle + 1;
}else return middle;
}
return -1;
}
左闭右开仍然用数学举例,区间[1,3)包含数字1,不包含3,代码也类似,right取数组长度即可
当left<right的时候,进入循环,开始二分查找
与左闭右闭不同的是,如果target在middle左侧,定义右区间的时候right = middle
并不需要死记硬背,这是闭和开的关系,right在这里为开区间,所以right = middle
剩下的与左闭右闭的逻辑基本相同,不再赘述
下面是力扣的例题,可以自己尝试写一个出来
移除数组元素
数组在计算机中存储的地址一般是连续的,这就导致 如果我们要删除数组中的一个元素的话,并不能直接抹除该元素,还要把删除元素后面的所有元素往前移一位。
以下是删除的第一种方法:使用两个for循环,第一个for循环负责找到目标元素,第二个for循环负责把元素后面的所有元素向前移
注意:删除执行完后要把i和size都自减,否则会漏元素
public static void main(String[] args) {
int[] arr = {1,2,3,4,3,2,1,5,3,0};
delete01(arr, 3);
}
public static void delete01(int[] arr, int target){
int size = arr.length;
for (int i = 0; i < size-1; i++){
if(arr[i] == target){
for (int j = i+1; j <size; j++) {
arr[j-1] = arr[j];
}
i--;
size--;
}
}
System.out.println("新数组为"+Arrays.toString(arr));
}
第二种方法:快慢指针法
使用两个指针,在一个for循环内完成数组的删除
public static void main(String[] args) {
int[] arr = {1,2,3,4,3,2,1,5,3,0};
delete02(arr,3);
}
public static void delete02(int[] arr, int target){
int slow = 0;
for (int fast = 0; fast <arr.length; fast++) {
if(arr[fast]!= target){
arr[slow] = arr[fast];
slow++;
}
}
System.out.println("新数组为"+Arrays.toString(arr));
}
}
第二种方法中,要理解fast指针和slow指针分别代表的含义。fast指针的作用是找符合新数组条件的元素(文中为除了3以外的所有元素),slow指针的作用是找到新数组元素的位置