704.二分查找
题目链接:704.二分查找
题目要求:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
定义:二分查找,又称折半查找,是一种在有序数组中查找特定元素的算法。它的基本原理是每次将待查找区间缩小一半,直到找到目标元素为止,或者确定目标元素不存在于数组中为止。
前提条件:给定的数组必须为有序数组,并且数组中没有重复元素,一旦有重复元素,返回的下标可能不是唯一的
难点:二分查找的主要难点和易错点在于如何选择搜索区间的边界条件,在选择边界条件时,一定要坚持区间不变量的原则(绝大部分while循环的条件都要坚持这个原则)
🤔解题思路
二分查找常见的区间边界为左闭有闭 [left,right] ,和左闭右开 [left,rigth)
💻I.左闭有闭 [left,right]
class Solution {
public int search(int[] nums, int target) {
//可以将边界条件带入到区间中看看是否符合 - [0,nums.length - 1]包含所有元素
int left = 0; //初始化区间左边界下标数组第一个元素 - 0
int right = nums.length - 1; //初始化区间右边界下标数组最后一个元素-nums.length - 1
int midle = -1; //初始化中间值
while(left <= right){ //退出循环的条件
midle = (left + right) / 2; //得到中间值
if(target > nums[midle]){
left = midle + 1; //搜索区间为左闭有闭,已经确定midle不是目标元素,所以left为midle的后一个元素,才能使新的搜索区间仍然满足[left,rigth]
}else if(target < nums[midle]){
right = midle - 1; //搜索区间为左闭有闭,已经确定midle不是目标元素,所以rigth为midle的前一个元素,才能使新的搜索区间仍然满足[left,rigth]
}else if(target == nums[midle]){
return midle;
}
}
return -1; //退出循环说明没有找到目标元素
}
}
图解:
①由于区间为左闭右闭 [left,right],所以 left 为数组第一个元素0,right为最后一个元素8,这样搜索区间就包含了全部待搜索元素 [0,8];
②因为①中midle<target,所以target一定在midle后面的位置,已经确定了midle(4)不会等于target(5),所以更新left为midle的后一个元素5,成为新的搜索区间 [5,8];(如果left更新为midle,则新区间 [4,8] 中midle(4)在下次查找中被重复查找了)
③因为②中midle>target,所以target一定在midle前面的位置,已经确定了midle(6)不会等于target(5),所以更新right为midle的前一个元素5,成为新的搜索区间 [5,5];(如果right更新为midle,则新区间 [5,6] 中midle(6)在下次查找中被重复查找了)
💻II.左闭右开 [left,rigth)
class Solution {
public int search(int[] nums, int target) {
//可以将边界条件带入到区间中看看是否符合 - [0,nums.length)包含所有元素
int left = 0; //初始化区间左边界下标数组第一个元素 - 0
int right = nums.length; //初始化区间右边界下标数组最后一个元素 - nums.length
while(left < right){ //退出循环的条件 - [left,rigth)
int midle = (left + right) / 2; //初始化中间值
if(target > nums[midle]){
left = midle + 1; //搜索区间为左闭有开,已经确定midle不是目标元素,所以left为midle的后一个元素,才能使新的搜索区间仍然满足[left,rigth)
}else if(target < nums[midle]){
right = midle; //搜索区间为左闭有闭,已经确定midle不是目标元素,所以rigth为midle,才能使新的搜索区间仍然满足[left,rigth)
}else if(target == nums[midle]){
return midle;
}
}
return -1; //退出循环说明没有找到目标元素
}
}
图解:
①由于区间为左闭右开 [left,right),所以 left 为数组第一个元素0,right为数组的长度9,这样搜索区间就包含了全部待搜索元素 [0,9);
②因为①中midle<target,所以target一定在midle后面的位置,已经确定了midle(4)不会等于target(5),所以更新left为midle的后一个元素5,成为新的搜索区间 [5,9);(如果left更新为midle,则新区间 [4,9) 中midle(4)在下次查找中被重复查找了)
③因为②中midle>target,所以target一定在midle前面的位置,已经确定了midle(7)不会等于target(5),所以更新right为midle所在的位置7,成为新的搜索区间 [5,7);(如果right更新为midle前一个位置,则新区间 [5,6) 中midle的前一个位置6不在下次查找区间,6被漏查了)
27. 移除元素
题目链接:27. 移除元素
题目要求:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
难点:需要原地移除目标元素,不能使用额外空间
🤔解题思路
💻I.暴力破解
使用双层for循环,外层用于遍历数组元素,内层用于移除目标元素,即将目标元素后面的所有元素前移,将目标元素覆盖
class Solution {
public int removeElement(int[] nums, int val) {
int size = nums.length; //数组长度
for(int i = 0;i < size;i++){ //外层循环,得到要移除的元素val
if(nums[i] == val){ //
for(int j = i + 1;j < size;j++){ //内层循环,将val后面所有的向前移一位
nums[j - 1] = nums[j];
}
size--; //数组有效长度-1,实际上数组的长度不变
i--; //位置为i的元素被覆盖,需要重新扫描新元素
}
}
return size; //新数组的有效长度
}
}
💻II.双指针
利用快慢指针,快指针用于遍历数组元素,慢指针指向有效元素待插入的位置
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0; //定义慢指针,指有效数组元素待插入的位置
for(int fast = 0;fast < nums.length;fast++){ //快指针,扫描数组元素
if(nums[fast] != val){ //查找有效元素(不是被移除的元素),并向前插入
nums[slow++] = nums[fast];
//上面代码等价于
//nums[slow] = nums[fast];
//slow++;
}
}
return slow; //慢指针所在位置下标就是数组有效长度
}
}
图解:以数组 [2,3,3,2]为例,移除值为3的元素
慢指针slow和快指针fast初始时同时指向第一个元素nums[0],slow指向待插入元素的位置,fast指向待判断元素;nums[0] != 3,将nums[fast]赋值给nums[slow],①快慢指针都向后移动;3为须删除元素,②所以slow不懂,fast后移;③同理;