给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
题目要求:使用二分查找找到目标值,并且最终返回目标值的下标
二分法
使用前提
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的
二分法思路为判断nums[middle]与target的大小关系
nums[mid]>target;目标在左边
更新右区间
nums[mid]<target;目标在右边
更新左区间
nums[mid]==target;
直接返回mid;
二分法的两种写法
左闭右闭[left,right]
左闭又开[left,right)
所取区间会影响到while循环里面的条件判断、当前位置在数组的意义
左闭右闭写法
左闭右闭区间需要注意
while(left<=right)
因为此时left==right有意义
if(nums[middle]>target)
{
right=middle-1;
}
如下图所示
![](https://i-blog.csdnimg.cn/blog_migrate/663dbb9e5da5866259cd4b28cea3f4e2.png)
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
while(left<=right){
int middle=left+((right-left)/2);// 防止溢出 等同于(left + right)/2
if(nums[middle]>target)
{
right=middle-1;
}
else if(nums[middle]<target)
{
left=middle+1;
}
else
{
return middle;
}
}
return -1;
}
};
左闭右开写法
while(left<right);
此时left=right没有意义
if(nums[mid]>target){
right=middle;
}
右开,所以可以[left,middle)
if(nums[mid]<target{
left=middle+1;
}
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { <
int middle = left + ((right - left) >> 1);
//位运算,运算速度更快,>>1等效于乘以1/2
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else {
return middle;
}
// 未找到目标值
return -1;
}
};
704后记
二分法思路
防止数值溢出的两种方法
int mid = left + ((right-left)/2);
int mid = left + ((right-left)>>1);
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
你不需要考虑数组中超出新长度后面的元素。
C++中数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
暴力解法
两层for循环,一层遍历数组元素,一层更新数组
时间复杂度O(n^2)
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--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
return size;
}
};
双指针(快慢指针法)
通过一个快指针和慢指针在一个for循环下完成暴力解法中两个for循环的工作:
遍历数组的同时更新数组
思路
定义快慢指针
快指针:寻找新数组的元素
慢指针:指向更新 新下标的位置
快指针在遇到待删除元素时,会继续移动
慢指针会停止不动
若之后快指针遇到的元素不是待删除元素,继续移动
慢指针会向前移动,指向快指针遇到的元素,此时快指针已经指在下一位元素
无论如何,快指针都会移动,可放在for循环语句中
快指针遇到待删除元素,会影响慢指针的操作
可放在if语句中,做判断的条件
由此我们可以写出
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;
}
};
双指针(左右指针法)
思路待补充
将右指针所指到的不等于val的元素覆盖左指针所指到的等于val的元素
/**
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int leftIndex = 0;
int rightIndex = nums.size() - 1;
while (leftIndex <= rightIndex) {
// 找左边等于val的元素
while (leftIndex <= rightIndex && nums[leftIndex] != val){
++leftIndex;
}
// 找右边不等于val的元素
while (leftIndex <= rightIndex && nums[rightIndex] == val) {
-- rightIndex;
}
// 将右边不等于val的元素覆盖左边等于val的元素
if (leftIndex < rightIndex) {
nums[leftIndex++] = nums[rightIndex--];
}
}
return leftIndex; // leftIndex一定指向了最终数组末尾的下一个元素
}
};