第一天 | 704. 二分查找、35. 收索插入位置、 34. 在排序数组中查找元素的第一个和最后一个位置、27. 移除元素
704. 二分查找
思路: 因为是有序数组,查找目标值,直接二分查找。要注意区间的确定。
int search(vector<int>& nums, int target) {
int i =0;
int j=nums.size();//左闭右开
while(i<j)//i>=j将没有意义
{
int mid = (i+j)/2;//取中间值
if(nums[mid]>target)//中间值大于target,说明target落在左半区间
{
j = mid;//移动右边界j至mid,因为是左闭右开,所以不能是mid-1,不然会漏掉nums[mid-1]这个值
}
else if(nums[mid]<target)//中间值小于target,说明target落在右半区间,移动左边界i
{
i=mid+1;//左闭右开,nums[mid]已经不等于target,所有从i为mid+1
}
else return mid;//如果恰好等于target直接返回
}
return -1;
}
35. 搜索插入位置
思路: 本题跟二分查找区别在于target不一定存在于数组中。但是依然可以用 二分法查找找到target应该存在的位置。如果在数组中找到了target直接返回当前索引,如果没有找到target,最后返回右边界就行,因为这是target最应该落入的位置,左边都小于taget,右边都大于target。
为什么一定是右边界而不是左边界:实际上都行, 因为左闭右开区间查找,左边界左边的值都一定小于target。while循环结束后,i一定等于j。而nums[j]一定是大于target的,这就意味着nums[i]==nums[j]>target。 nums[i] 左边的值都一定小于target。所以target插入i或者j都是一样的。
int searchInsert(vector<int>& nums, int target) {
int i=0;
int j=nums.size();
while(i<j)
{
int mid=(i+j)/2;
if(nums[mid]>target)
{
j=mid;
}
else if(nums[mid]<target)
{
i=mid+1;
}
else return mid;
}
return j;
}
34. 在排序数组中查找元素的第一个和最后一个位置
思路: 本题找的是一个区间,而不是一个数。所以一开始很难跟二分查找联系起来。但是仔细想想。我们找target的区间,实际上可以拆分为2个任务。
1.找到第一个大于等于target的位置left
2. 找到第一个大于target的位置right
,right-1
即为最后一个大于target的位置。
最后返回[left, right-1]
如何查找第一个大于target的位置?
在上一题,搜索插入位置,实际上就是查找一个左边小于target右边大于target的位置。那么查找第一个大于target的位置,我们只需要找到一个位置,这个位置的左边的值都小于等于target,右边的值都大于target。
int findborder(vector<int>& nums, int target)
{
int l = 0;
int r = nums.size();
while(l<r)
{
int mid = l+((r-l)>>1);
if( nums[mid]>target ) r= mid;
else l=mid+1;
}
return l;//return r 也可以
}
如何查找第一个大于等于target的位置?
原理同上,我们只需要找到一个位置,这个位置的左边的值都小于target,右边的值都大于等于target。
int findborder(vector<int>& nums, int target)
{
int l = 0;
int r = nums.size();
while(l<r)
{
int mid = l+((r-l)>>1);
if( nums[mid]>=target) ) r= mid;
else l=mid+1;
}
return l;
}
将上面2段代码整合一下
int findborder(vector<int>& nums, int target, bool find)//find为true该函数查找第一个等于taregt的位置
{
int l = 0;
int r = nums.size();
while(l<r)
{
int mid = l+((r-l)>>1);
if( (find&&nums[mid]>=target) || nums[mid]>target ) r= mid;
else l=mid+1;
}
return l;
}
最后判断一下,找到的
left
存在于数组范围内,且nums[left]==target
.因为如果数组中不存在等于target的子串,那么left就是大于等于target的位置,甚至会超出数组范围。
if(left>=0 && left<size && nums[left]==target ) return {left, right};
return {-1,-1};
整体代码如下:
class Solution {
int findborder(vector<int>& nums, int target, bool find)
{
int l = 0;
int r = nums.size();
while(l<r)
{
int mid = l+((r-l)>>1);
if( (find&&nums[mid]>=target) || nums[mid]>target ) r= mid;
else l=mid+1;
}
return l;
}
public:
vector<int> searchRange(vector<int>& nums, int target) {
int size = nums.size();
int left = findborder(nums, target, true);
int right = findborder(nums, target, false)-1;//注意这里减一
if(left>=0 && left<size && nums[left]==target ) return {left, right};
return {-1,-1};
}
};
27. 移除元素
思路: 本题要求原地修改数组。直观的想法就是把数组后面不等于val的元素跟前面等于val的元素对调一下。所以我用首尾指针的方法来实现。
int removeElement(vector<int>& nums, int val) {
if(nums.size()==0) return 0;//如果为空数组直接返回0
int i=0;
int j = nums.size()-1;
while(i<j)
{
while(i<j&&nums[i]!=val) i++;
while(i<j&&nums[j]==val)j--;
if(i<j) swap(nums[i++] , nums[j--]);
}
if(nums[i]!=val) i++;//对调后,有一种情况是i==j且nums[i]!=val,需要把i再往前移一格
return i;
}
总结
今天前三题都是二分查找。逐渐加深对二分查找的理解。最后一题复习了双指针。
我对二分查找的理解:二分查找查找的是符合划分条件的位置。
例如 704. 二分查找 实际上我可以查找第一个大于等于target的位置。最后判断该位置是否在数组范围内且等于target.
class Solution {
public:
int search(vector<int>& nums, int target) {
int i =0;
int j=nums.size();
while(i<j)
{
int mid = (i+j)/2;
if(nums[mid]>=target) j = mid;
else i=mid+1;
}
if(i<nums.size()&& i>=0&&nums[i]==target) return i;
return -1;
}
};
- 搜索插入位置 同上,程序可以几乎不变
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int i=0;
int j=nums.size();
while(i<j)
{
int mid=(i+j)/2;
if(nums[mid]>=target) j=mid;
else i=mid+1;
}
return i;
}
};
- 在排序数组中查找元素的第一个和最后一个位置 多了一步,查找第一个大于target的位置。
class Solution {
int findborder(vector<int>& nums, int target, bool find)
{
int l = 0;
int r = nums.size();
while(l<r)
{
int mid = l+((r-l)>>1);
if( (find&&nums[mid]>=target) || nums[mid]>target ) r= mid;
else l=mid+1;
}
return l;
}
public:
vector<int> searchRange(vector<int>& nums, int target) {
int size = nums.size();
int left = findborder(nums, target, true);
int right = findborder(nums, target, false)-1;//注意这里减一
if(left>=0 && left<size && nums[left]==target ) return {left, right};
return {-1,-1};
}
};