704. 二分查找
状态:之前学过二分法,但只是了解概念,属于有思路但一写就废,也并不知道其中的条件判断的细节。
解题思路:
1.使用二分法查找,根据target的值与左右指针的比较,缩小比较的区间,而这个区间的边界确定是比较重点的部分
2.切入二分法的条件:升序,无重复元素
代码实现:
1.左闭右闭写法
class Solution {
public:
int search(vector<int>& nums, int target) {
int left =0;
int right = nums.size()-1; //从这可以看出是左闭右闭
while(left<=right) //因为是左闭右闭,因此可以左右相等是有意义的
{
int mid = left + ((right-left)>>1); //防止超过int最大值
if(nums[mid]>target)
right = mid -1; //更新右区间,因为可以取到,而mid已经参与比较了,因此-1
else if(nums[mid]<target)
left = mid +1 ; //同理+1
else return mid; //当找到就返回下标
}
return -1; //未找到就返回-1
}
};
2.左闭右开写法
class Solution {
public:
int search(vector<int>& nums, int target) {
int left =0;
int right = nums.size(); //右区间不能取到
while(left<right) //左右相等无意义
{
int mid = left + ((right-left)>>1);
if(nums[mid]>target)
right = mid; //更新右区间时,因为右区间取不到,不需要-1
else if(nums[mid]<target)
left = mid +1 ;
else return mid;
}
return -1;
}
};
时间复杂度:O(log n)
空间复杂度:O(1)
总结:
1.循环不变量
这里的不变量是定义的区间,是左闭右闭,还是左闭右开,这是不变的,在后续改成区间值的时候,也不变
2.判断条件的确定
这里有两个需要确定的地方,一个是while中对于left和right的判断
当左右都闭时,例如[2,2],左右区间相等时,这是有意义的,因此为left<=right
当左闭右开时,例如[2,2),左右区间相等是没有意义的,因此判断条件为left<right
其次是对于边界的确定:
当左闭右闭时,因为在if中已经对于mid下标所在值进行了比较了,因此后续这个下标并不需要再参与比较,而因为左和右都是闭的,是可以取到的,因此左边界修改要mid加1,右边界修改要mid-1
当左闭右开时,因为右边是取不到的,因此右边界修改要变成mid,左边界为mid+1
3.本质(个人理解)
在左闭右闭的情况下,将条件改为nums[mid]>=target时,是向左搜索趋势,记录最后一个<=target的数,先right=mid-1,再可以使用leftBoard来记录right(左趋势),这里注意如果nums里有target,那么leftBoard+1才是target的下标,如果没有,那么leftBoard是最后一个<target的数的下标
同理:条件为nums[mid]<=target时,是向右搜索趋势,记录第一个>=target的数,先left = mid +1,再使用rightBoard来记录left(右趋势),nums有target,那么rightBoard-1是target的下标,如果没有,rightBoard是第一个>target的数的下标
27.移除元素
题目链接:. - 力扣(LeetCode)
文章讲解:代码随想录
视频讲解:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili
状态:平时都是直接用库函数,而并不知道原理,因此一写就废
需要了解的知识:数组在内存空间是连续的,因此对于移除某个元素,实际上是覆盖操作,在C++中二维数组内存地址也是连续的
解题思路:
1.暴力解法
代码实现:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int count = nums.size();
for(int i=0;i<count;i++)
{
if(nums[i] == val) //当查询到目标值时,将目标值后面的所有值往前覆盖一个
{
for(int j= i+1; j<count;j++)
{
nums[j-1] = nums[j];
}
i--; //注意i一定要减一,再次判断这个下标
count--; //数组长度-1
}
}
return count;
}
};
需要注意的是:i--是必须的,否则遇到连续目标值的出现,会发生错误
时间复杂度:O(n^2)
空间复杂度:O(1)
2.双指针法
代码实现:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0; //定义一个慢指针
for(int fast =0;fast<nums.size();fast++) //外层循环是快指针
{
if(nums[fast]!=val) //当快指针不等于目标值时,填入慢指针的位置
{
nums[slow++] = nums[fast];
}
}
return slow; //这里的slow是已经++过的,因此其在数值上等于数组的长度
}
};
时间复杂度:O(n)
空间复杂度:O(1)
总结:
1.暴力解法
该思路中,实现较为简单,但需要注意的是这个i--,再删除完一个元素后,这个元素的下标值要重新判断以便,以防重置目标值的出现
2.双指针的思想
快慢两个指针,可以在一个for循环中,完成两个for循环所做的事
快指针:用于寻找新数组的元素,我的理解是:找符合条件的萝卜,去前方探路
慢指针:用于记录新数组的下标,我的理解是:这是一个一个坑,由快指针找到的萝卜来填入,后方更进。
收获就是觉得每天有事干了,很充分,这种知识进入大脑的感觉很喜欢┭┮﹏┭┮,后面会持续坚持的,ヾ(◍°∇°◍)ノ゙加油