前提
1、有序序列
2、无重复元素值
两个注意点
1、时刻记住自己的区间定义,区间定义关系到自己的代码该怎么写
2、如何判断right = mid
还是 right = mid - 1
区间定义通常有两种:
- [left, right]
- [left, right)
题目:leetcode 704 二分查找
题意:给定一个数组,查找目标值是否在数组中,存在则返回数组小标,否则返回-1
思路 1:暴力搜索 - 复杂度O(n) 直接遍历整个数组,判断是否存在目标值即可
class Solution {
public:
int search(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == target) {
return i;
}
}
return -1;
}
};
思路 2: 二分查找
第一种二分: 区间定义为[left, right]
这种定义方式意味着 while循环条件 为while(left <= right)
,因为两边都是闭区间,可以取到right, 同时可以知道此时right初始值为right = nums.size() - 1
size函数是返回容器中元素的个数,而数组下标是从0开始的,所以
right = nums.size() - 1
接下来进行判断,if(nums[mid] > target)
,说明区间过了,下次查询应该在左区间中,其中左区间的right为right = mid - 1
原因:由刚刚的if条件判断知道,当前的nums[mid]肯定不是我们要找的target,所以在下次查询是要排除它,又因为区间是左闭右闭的,所以
right= mid - 1
接下来判断if(nums[mid] < target)
,同样说明区间过了,下次查询应该在右区间,其中右区间的left为left = mid + 1
原因:同样当前的nums[mid]肯定不是我们要找的target,所以在下次查询是要排除它,又因为区间是左闭右闭的,所以
left = mid + 1
最后判断if(nums[mid] == target)
,那么返回mid值return mid;
,此时的mid 即为目标值的下标
完整代码如下:
第一种二分
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + ((right - left) / 2);
if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
};
第二种二分: 区间定义为[left, right)
这种定义方式意味着 while循环条件 为while(left < right)
,因为right是开区间,取不到right ,同时可以知道,此时right初始值为right = nums.size()
接下来进行判断,if(nums[mid] > target)
,说明区间过了,下次查询应该在左区间中,其中左区间的right为right = mid
原因:由刚刚的if条件判断知道,当前的nums[mid]肯定不是我们要找的target,所以在下次查询是要排除它,又因为区间是左闭右开的,所以
right= mid
接下来判断if(nums[mid] < target)
,同样说明区间过了,下次查询应该在右区间,其中右区间的left为left = mid + 1
原因:同样当前的nums[mid]肯定不是我们要找的target,所以在下次查询是要排除它,又因为区间是左闭右闭的,所以
left = mid + 1
最后判断if(nums[mid] == target)
,那么返回mid值return mid;
,此时的mid 即为目标值的下标
第二种二分
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) {
int mid = left + ((right - left) / 2);
if (nums[mid] > target) {
right = mid ;
} else if(nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
};
写在最后
第一次写二分的时候把int mid = left + ((right - left) / 2);
写到while循环外面了,导致超时,因为mid值一直没更新
总结:二分查找重要的就是时刻记住自己的区间定义,是左闭右闭还是左闭右开,确定了区间定义后,后面的while循环条件以及right的值right = mid
还是 right = mid - 1
都可以迎刃而解
leetcode27 移除元素
题意:给定一个数组,一个目标值,删除数组中存在的目标值,返回数组的的大小 思路1:暴力方法 —— 时间复杂度O(n2)
两层 for 循环,第一层循环遍历整个数组,条件判断
nums[i] == val
,第二层 for 循环,将数组中所有元素往前挪一位,结束后i--
的原因是
i原来所在的位置被新的数覆盖了,这个新的数也要判断是不是等于目标值,即i++ 和 i--
抵消了
完整代码如下:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for(int i = 0; i < size; i++) {
if (nums[i] == val) {
for(int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
i--;
size--;
}
}
return size;
}
};
思路2:双指针算法——时间复杂度O(n)
fastIndex :寻找新数组元素,即不等于目标值的元素
slowIndex :指向新数组最后一个元素的位置
一层for循环让快指针fastIndex动起来 ,条件判断
nums[fastIndex] != val
,因为当前指向的值与目标值不相等,所以赋值nums[slowIndex] = nums[fastIndex]
,slowIndex++
,这里当出现指向的值与目标值相等的时候,fastIndex还会向前走slowIndex则不动,然后把fastIndex指向的值覆盖slowIndex指向的值
完整代码如下:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for(int fastIndex = 0; fastIndex < nums.size(); fastIndex++){
if (nums[fastIndex] != val){
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
};
总结:
今天对二分查找方法相比之前又清楚了很多,主要归功于第一次开始写博客,可以把自己的理解写下来供自己以后来看。
移除元素题第一眼暴力解法有些问题,看了题解后可以很顺利地写出来,双指针求解还需要再多消化消化
注明:该博客同样分享在我的掘金账号Day 01| 二分查找算法及题目分析 - 掘金