目录:
题目链接:
https://leetcode.cn/problems/binary-search/
https://leetcode.cn/problems/remove-element/
文章链接:
数组理论基础:https://programmercarl.com/数组理论基础.html#数组理论基础
时间复杂度基础:https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html
文章讲解:https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html
https://programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html
视频讲解:
https://www.bilibili.com/video/BV1fA4y1o715
https://www.bilibili.com/video/BV12A4y1Z7LP
需要找到升序数组中目标值的序号,没有则返回-1。算法的复杂度为O(log n)。
如果暴力求解的话,从头找到尾,那算法复杂度应该是O(n)。这题用二分法来求解,先找到中间的值,进行判断。之后根据大小选择是左边还是右边部分,一直这样分下去,最后找到目标值。
给出一个数组nums和一个整数val,要求删除数组中所有的val,然后返回剩下的数组k的长度和数组。
题目还有要求,不要为另一个数组额外分配空间,所以是就在这个数组上进行操作。
思考1: 暴力求解,从前往后或者从后往前遍历,如果数组中有值等于val,那么直接将后面的所有往前移一位。直到遍历完。
思考2:无
区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。
要注意是按照左闭右闭的思路写的还是按照左闭右开的思路写的。
主要是因为**.size()函数可以返回容器中实际数据的个数,也就是向量数组的长度。**比如数组长度为6,但是由于从0开始技术,num[6] 是不存在的。所以根据右边区间在哪个位置,就有两种写法。
主要实现代码:
区间取左闭右闭的情况
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)/2;
if(target<nums[mid]){
//当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
right = mid-1;
}
else if(target>nums[mid]){
//当前这个nums[middle]一定不是target,那么接下来要查找的右区间结束下标位置就是 middle + 1
left = mid+1;
}
else{
return mid;
}
}
//未找到目标值
return -1;
}
};
区间取左闭右开的情况
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(target < nums[mid]){
right = mid;
}
else if(target > nums[mid]){
left = mid + 1;
}
else{
return mid;
}
}
//未找到目标值
return -1;
}
};
两个方法实现的用时和内存消耗情况:
上面为左闭右开情况,下面为左闭右闭情况。可以看到,当使用左闭右开区间时,运行速度更快,初步推测是因为左闭右闭在while中,比左闭右开的情况多了移步的操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1P9VGkLO-1684937370361)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/90eed747-80ff-4654-9f51-9f97001a1c62/Untitled.png)]
题目:给你一个数组 nums
**和一个值 val
,你需要 原地 移除所有数值等于 val
**的元素,并返回移除后数组的新长度。
暴力解法,就是找到对应元素后,将后面所有的元素往前移一位。
这里有个坑,下面代码中,第二个for循环,j的初始值选取,如果直接等于i,需要注意数组的长度问题。要往后选大的一位。除此之外,还要注意找到对应元素并移除后,下标需要再退到之前的一位,并且总长度要减少1.
暴力解法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int length = nums.size();
for(int i = 0; i < length; i++){
if(nums[i] == val){
//for(int j = i; j
// nums[j] = nums[j+1];
// }
for(int j = i+1; j<length;j++){
nums[j-1] = nums[j];
}
i--;
length--;
}
}
return length;
}
};
双指针法(快慢指针法):
通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
没有碰到要删除的值的时候,两个指针一起走,一直用快的指针给慢的指针赋值。碰到要删除的元素时,慢的指针停下,快的指针继续走一步,之后又同时赋值。确实牛逼。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowindex = 0;
for(int fastindex = 0 ; fastindex < nums.size(); fastindex++){
if(val != nums[fastindex]){
nums[slowindex++] = nums[fastindex];
}
}
return slowindex;
}
};
虽然两者运行时间差不多,但是暴力求解的时间复杂度为O(n^2),而双指针法的时间复杂度仅为O(n)。数据量大的时候就能有明显差距。
1、C++掌握不熟悉,主要是vector部分没看过
2、没有考虑到区间问题
3、left + ((right -left) >> 1) == (left + right) /2 相当于二进制右移,这两种写法是等价的
1、掌握二分法,区间的注意事项。
2、双指针法