704. 二分查找
数组需要两点注意的是
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的
解法1 左闭右闭
有效区间是【left,right】,所以当经过一次if-else判断后不能包含nums[mid](因为它已经参与过判断),所以应该是left=mid+1; right=mid-1;
class Solution {
public int search(int[] nums, int target) {
int left=0;
int right=nums.length-1;
while(left<=right){
int mid=(left+right)>>>1;
if(nums[mid]<target){//目标值在右边
left=mid+1;//左闭右闭,说明mid已经是被搜索过的,因此不要包含
}
else if(nums[mid]>target){
right=mid-1;
}
else {
return mid;
}
}
return -1;
}
}
解法2 左闭右开
有效区间是【left,right),当target在下标mid的右边时,始终牢记左闭,说明mid是被判断过的(这个逻辑参考解法1),故 left=mid+1;当target在下标mid的左边时,始终牢记右开,说明mid是没有被判断过的,故right=mid
class Solution {
public int search(int[] nums, int target) {
int left=0;
int right=nums.length;
while(left<right){
int mid=(left+right)>>>1;
if(nums[mid]<target){//目标值在右边,【left,right)
left=mid+1;//左闭右开,说明mid是被搜索过的,因此不要包含
}
else if(nums[mid]>target){//目标值在左边
right=mid;//左闭右开,说明mid是没被搜索过的,要包含
}
else {
return mid;
}
}
return -1;
}
}
总结:开头对有效区间的定义,既决定了while循环里的条件也决定了left和right的变化
27. 移除元素
双指针解法,之前做过,今天脑袋抽风居然没写对,我还是基础不扎实啊!
关键点是fast指针每次判断,当遇到nums[fast]!=val时slow指针才会移动
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];
slow++;
}
}
return slow;
}
}
35. 搜索插入位置
依旧是三种情况,要找的是插入位置,类似于找一个边界问题的,搜索区间左闭右闭
因此最后退出循环的情况应该是right=left-1;就是right指针回在left的右边,因此这里返回left或者right+1都是可以的
class Solution {
public int searchInsert(int[] nums, int target) {
//寻找一个左边界的问题,nums 为 无重复元素 的 升序排列数组
int left=0;
int right=nums.length-1;
while(left<=right){
int mid=(left+right)>>>1;
if(nums[mid]<target){//
left=mid+1;
}
else if(nums[mid]>target){//
right=mid-1;
}
else{//目标值等于数组中的某一个元素
return mid;
}
}
//最后的退出循环的情况应该是right=left-1;就是right指针回在left的右边,因此这里返回left也是可以的
return left;
}
}
解法2 这是我做完34题回来看35题,其实35题就是寻找左边界的问题
class Solution {
public int searchInsert(int[] nums, int target) {
int len=nums.length;
if(target>nums[len-1]){return len; }
if(target<=nums[0]){return 0;}
int left=0;
int right=nums.length-1;
while(left<=right){
int mid=(left+right)>>>1;
if(nums[mid]>=target){
right=mid-1;
}
else if(nums[mid]<target){
left=mid+1;
}
}
return right+1;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
思路:需要寻找到右边界和左边界,在寻找左边界时,如果如果当前nums[mid]==target,就需要再进一步看看nums[mid-1]还是不是target,因此 if(nums[mid]>=target){//寻找左边界这里需要等号;
在寻找右边界时,只有if(nums[mid]>target)遇到大于的情况,才会移动右边界,因此,最终right一定指向target,但是left一定是指向右边界的右一位元素
举个例子
“3 4 5 8 8 9”
class Solution {
public int[] searchRange(int[] nums, int target) {
//二分法查找
int leftIdx=leftBorder(nums,target);
int rightIdx=rightBorder(nums,target)-1;//注意这里的减1
System.out.println(leftIdx);
System.out.println(rightIdx);
if(leftIdx<=rightIdx&&rightIdx<nums.length&&nums[leftIdx]==target&&nums[rightIdx]==target){
return new int[]{leftIdx,rightIdx};
}
return new int[]{-1,-1};
}
//找左边界
public int leftBorder(int[] nums, int target){
//
int left=0;
int right=nums.length-1;
int ans=0;
while(left<=right){
int mid=(left+right)>>>1;
if(nums[mid]>=target){//寻找左边界
right=mid-1;
ans=mid;//可能是要找的左边界
}
else{
left=mid+1;
}
}
return ans;
}
//找右边界
public int rightBorder(int[] nums, int target){
//
int left=0;
int right=nums.length-1;
int ans=nums.length;
while(left<=right){
int mid=(left+right)>>>1;
if(nums[mid]>target){
right=mid-1;
ans=mid;
}
else{
left=mid+1;
}
}
System.out.println(ans);
return ans;
}
}
注意点
开始我把ans初始化为0,在用例nums:[1],target=1卡住了,
然后把寻找右边界的地方ans=nums.length就AC了,寻找左边界还是ans=0;
原因是在寻找右边界时,本身nums[mid]==target了,直接将left=1然后退出循环了,返回的是0,但是在主函数中对右边界减去1,所以最终的 leftIdx=0,rightIdx=-1,必然出错
所以我们在寻找右边界方法时要把ans设置成nums.length
在做完这道题后回去看35题其实是一样的思路