标题
704.二分查找
题目链接:704.二分查找
思路:看到有序无重复序列查找就可以用二分查找
左闭右闭和左闭右开就像在右边是否加头节点一样
左闭右闭区间方法:
因为定义target在[left, right]区间,所以有如下两点:
while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=;
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1;
class Solution {
public int search(int[] nums, int target) {
//定义target在左闭右闭的区间里,[left, right]
int left=0,right=nums.length-1;
//当left==right,区间[left, right]依然有效,所以用 <=
while(left<=right){
int mid=(left+right)/2;
//找到target,返回下标
if(nums[mid]==target)return mid;
//target 在左区间,所以[left, middle - 1]
else if(nums[mid]>target){
right=mid-1;
}
//target 在右区间,所以[middle+1, right]
else{
left=mid+1;
}
}
//未找到目标值
return -1;
}
}
左闭右开区间方法:
while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的;
if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle];
class Solution {
public int search(int[] nums, int target) {
//定义target在左闭右开的区间里,[left, right)
int left=0,right=nums.length;
//当left==right,区间[left, right)无效,所以用 <
while(left<right){
int mid=(left+right)/2;
//找到target,返回下标
if(nums[mid]==target)return mid;
//target 在左区间,所以[left, middle)
else if(nums[mid]>target){
right=mid;
}
//target 在右区间,所以[middle+1, right)
else{
left=mid+1;
}
}
//未找到目标值
return -1;
}
}
35.搜索插入位置
题目链接:35.搜索插入位置
思路:查找序列中已有的数直接用二分查找,对不在序列中的数再作处理
比如:左闭右闭方法中在1,3,5,6中插入4,区间变化[0,3]->[2,3]->[2,1],left>right,结束循环,插入位置为2;在1,3,5,6中插入2,[0,3]->[0,0],left==right,结束循环,插入位置为1。
代码随想录讲解
这道题目,要在数组中插入目标值,无非是这四种情况。
目标值在数组所有元素之前
目标值等于数组中某一个元素
目标值插入数组中的位置
目标值在数组所有元素之后
左闭右闭方法:
class Solution {
public int searchInsert(int[] nums, int target) {
//定义左闭右闭区间
int left=0,right=nums.length-1;
//因为是[left,right]区间,left==right有效,所以是<=
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]==target)return mid;
//target在右区间,在[mid+1,right]中
else if(nums[mid]<target){
left=mid+1;
}
//target在左区间,在[left,midt-1]中
else{
right=mid-1;
}
}
//不在序列中
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right + 1
// 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
return right+1;
}
}
左闭右开方法:
class Solution {
public int searchInsert(int[] nums, int target) {
//定义左闭右开区间
int left=0,right=nums.length;
//因为是[left,right)区间,left==right无效,所以是<
while(left<right){
int mid=(left+right)/2;
if(nums[mid]==target)return mid;
//target在右区间,在[mid+1,right)中
else if(nums[mid]<target){
left=mid+1;
}
//target在左区间,在[left,midt)中
else{
right=mid;
}
}
//不在序列中
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, 0)
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right),return right;
// 目标值在数组所有元素之后的情况 [left, right), 因为是右开区间,所以 return right;
return right;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
题目链接:34
思路:二分查找找到target,再往左,往右找,起始坐标和结束坐标,但时间复杂度为o(n)。
代码随想录讲解
寻找target在数组里的左右边界,有如下三种情况:
情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
阅读后的理解:
利用两次二分查找分别找到大于等于tar,和小于等于tar的两个坐标。
比如:5,7,7,8,8,10,target=8。
确定左边界的过程:
[0,5]—2,7—left=mid+1;
[3,5]—4,8—right=mid-1;
leftborder=right=3,left=3;
[3,3]—3,8—right=mid-1;
leftborder=right=2;
即,从[left,right]不断查找,如果中间值大于等于target,就往左减少区间(right=mid-1),并更新leftborder=right,如果中间值小于target,就往右减少区间(left=mid+1),但不更新leftborder。
如果数组中的元素都比target小,leftborder就不赋值,没有左边界,就属于情况一。
确定右边界的过程:
[0,5]—2,7—left=mid+1;
rightborder=left=3;
[3,5]—4,8—left=mid+1;
rightborder=left=5;
[5,5]—5,10—right=mid-1;
最后leftborder在target前一位,rightborder在target后一位
依次处理三种情况,情况一:leftborder或者rightborder其中任意一个没赋值
情况二:leftborder与rightborder之差小于1
情况三:leftborder与rightborder之差大于1
class Solution {
public int[] searchRange(int[] nums, int target) {
int leftBorder=getLeftBorder(nums,target);
int rightBorder=getRightBorder(nums,target);
//情况一;target 在数组范围的右边或者左边
if(leftBorder==-2||rightBorder==-2)return new int []{-1,-1};
//情况三
if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
//情况二
return new int[]{-1, -1};
}
//二分查找确定左边界
public int getLeftBorder(int[]nums,int target){
//初始化左边界值,如果没有,则为-2
int leftBorder=-2;
//初始化左闭右闭查找区间
int left=0,right=nums.length-1;
//因为是[left,right],left==right有意义,所以<=
while(left<=right){
int mid=(left+right)/2;
//target在右区间,更新区间[mid+1,right]
if(nums[mid]<target){
left=mid+1;
}
else{
right=mid-1;
leftBorder=right;
}
}
return leftBorder;
}
//二分查找确定右边界
public int getRightBorder(int[]nums,int target){
//初始化右边界值,如果没有,则为-2
int rightBorder=-2;
//初始化左闭右闭查找区间
int left=0,right=nums.length-1;
//因为是[left,right],left==right有意义,所以<=
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]>target){
right=mid-1;
}
// 寻找右边界,nums[middle] == target的时候更新left
else{
left=mid+1;
rightBorder=left;
}
}
return rightBorder;
}
}
27.移除元素
题目链接:27.移除元素
思路:因为写过很多次,直接想到快慢指针,快指针遍历原数组,慢指针修改新数组。
class Solution {
public int removeElement(int[] nums, int val) {
//定义快慢指针
int left=0,right=0;
//快指针遍历数组
while(right<nums.length){
//如果快指针当前指向值不等于需要移除的值
if(nums[right]!=val){
//令慢指针当前值等于快指针的值
nums[left]=nums[right];
//快慢指针同时移动
right++;
left++;
}
//如果快指针当前指向值需要移除,则快指针移动一
else{
right++;
}
}
return left;
}
}
今日总结
虽然对二分查找很熟悉,但以前写的很乱,没搞清楚边界条件,今天写三道题,前两道都分别用了左闭右闭和左闭右开两种方式,左闭右闭直接从数组中开始二分,左闭右开相当于在右边加了一个空的头节点,所以left=right没有意义。35题主要是搞清楚最后返回插入位置时为什么是和right有关,34题主要搞清楚二分查找怎么在O(logn)时间复杂度里找到target的首坐标和尾坐标。