代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素
704.二分查找
题目链接:704.二分查找
目的:在一个有序数组中查找一个元素target
二分基础方法有两种,主要是根据每次范围变化时的区间变化情况分为左闭右闭和左闭右开:
- 左闭右闭
[left, right]
class Solution {
public:
int search(vector<int>& nums, int target) {
if (nums.size() == 0) return -1;
if (target < nums[0] || target > nums[nums.size() - 1]) return -1;
// 二分查找
int left = 0;
int right = nums.size() - 1;
while (left <= right) { // [left,right]情况下left==right的时候有意义
int middle = left + (right - left) >> 2;
if (nums[middle] > target) right = middle - 1;
else if (nums[middle] < target) left = middle + 1;
else return middle;
}
}
return -1;
}
- 左闭右开
[left, right + 1]
int left = 0;
int right = nums.size();
while (left < right) {
int middle = left + (right - left) / 2;
if (nums[middle] > target) right = middle;
else if (nums[middle] < target) left = middle + 1;
else return middle;
}
这两个是二分查找的基础写法,具体题目中有些需要进行变换,比如while
循环里面是否相等,以及更新左右边界,可以模拟一下进行思考。
补充题目:
题目链接:[35.搜索插入位置](35. 搜索插入位置 - 力扣(LeetCode))
目的:一个有序数组查找target,若存在返回索引不存在返回应该插入的位置。
思路:其实相比与基础二分查找,要考虑的就是跳出循环后应该返回哪个作为其应该插入的位置。若是左闭右闭写法,最后在区间[left, right]
时,left==right==mid时
,若nums[mid] < target
,说明此时left和right指向的元素小于target,移动left,target应该插入此时的left位置。同理,若nums[mid]>target
,说明此时left和right指向的元素大于target,移动right,target应该插入此时的left位置。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() -1;
while (left <= right) {
int middle = left + (right - left) / 2;
if (nums[middle] > target) {
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
return left;
}
};
题目链接:[34.在排序数组中查找元素的第一个和最后一个位置](34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode))
题干:一个非递减数组和一个目标值,找出目标值在数组中的起始和结束位置,若不存在返回[-1,-1]
思路:分别查找左边界和右边界。如何找左边界,其实就是要让左边界一致存在查找区间[left, right]
中。以查找左边界为例,与基础二分查找不同处在于当nums[mid]==target
的时候区间的变化,查找左边界的时候,此时应该变换right,才能保证左边界还在区间内。
边界的处理:主要学习的是查找不到时候的边界处理,查找不到有三种情况,一是target比最小的元素还小,二是target比最大的元素还大,三是target在第一个元素和最后一个元素之间但是不存在数组里。**处理方式:**用变量纪录左右边界,初始值设置为-2,防止覆盖。以左边界为例,是将左边界设置为right的值,因此最后找到是左边界-1,最终设置左边界为right+1
。最终若左右边界有一个未更新,则说明是情况一二,若差值小于0则是情况三,否则说明存在对应的区域。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
// 左闭右闭
int leftBorder = -2, rightBorder = -2;
int left = 0;
int right = nums.size() - 1;
// 寻找左边界
while (left <= right) {
int mid = left + (right - left ) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
leftBorder = right;
}
}
// 找右边界
left = 0;
right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left ) / 2;
if (nums[mid] <= target) {
left = mid + 1;
rightBorder = left;
} else {
right = mid - 1;
}
}
std::cout << leftBorder << rightBorder << endl;
// 返回值
if (rightBorder - leftBorder < 2 || leftBorder == -2 || rightBorder == -2) return {-1, -1};
else return {leftBorder + 1, rightBorder - 1};
}
};
27.移除元素
题目链接:[移除元素](27. 移除元素 - 力扣(LeetCode))
题干:一个数组nums
和一个值val
,需要原地移除所有数值等于val
的元素,然后返回最后的数组大小。
刚开始写的暴力算法:
int count = nums.size();
cout << count << endl;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == val) {
count--;
cout << count << endl;
for (int j = i + 1; j < nums.size(); j++) {
nums[j - 1] = nums[j];
}
}
}
cout << count << endl;
return count;
错误原因,数组中移除元素是通过将后面的元素覆盖前面的,但是最后的元素还是保持不变,遍历到最后面的元素后还是会进入循环造成错误,比如3223,移除了第一个数组变为2233,遍历到最后一个元素就多进行计算了。
正确暴力解法:从后往前遍历,就不会有上面的问题
int count = nums.size();
for (int i = nums.size() - 1; i >= 0; i--) {
if (nums[i] == val) {
count--;
for (int j = i + 1; j < nums.size(); j++) {
nums[j - 1] = nums[j];
}
}
}
return count;
双指针写法: o ( n ) o(n) o(n)
int j = 0;
// for (int i = 0, j = 0; i < nums.size(); i++) {
for (int i = 0; i < nums.size(); i++) {
if (nums[i] != val) {
nums[j++] = nums[i];
cout << j << endl;
}
}
return j;
这里刚开始又犯了一个错误,在for循环内重新给j赋值了j=0
,会造成for循环里面的是一个局部变量,隐藏了外面的变量j,而最后返回的一直是外面的j,一直为0。
另一种双指针写法:宝宝想的,总体思路是找到左边等于val的,然后右边不等于val的,将右边值赋给左边,最终返回左边的值,相当于从两边同时进行查找。
int left =0,right = nums.size()-1;
if(nums.size() == 0) return 0;
if (nums.size() == 1 && nums[0] == val) return 0;
while(left<= right){
if(nums[left]== val){
while(nums[right]== val && right > left){
right--;
}
if (right <= left) break;
if(nums[right]!=val && right >left){
nums[left]= nums[right];
right--;
}
}
left++;
}
return left;