代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素
704.二分查找
二分查找的使用前提是有序数组并无重复数字
在写二分查找时,关键是搞明白具体区间的左右开闭
自己首先写一版出来
首先自己想到的就是递归,写的代码如下,一次ac:
class Solution {
public:
int search(vector<int> &nums, int target) {
int num = nums.size();
return search2(nums, 0, num - 1, target);
}
private:
int search2(vector<int> &nums, int low, int high, int target) {
if (low > high) return -1;
int mid = low + (high - low) / 2;
if (nums[mid] > target) return search2(nums, low, mid-1, target);
if (nums[mid] == target) return mid;
if (nums[mid] < target) return search2(nums, mid+1, high, target);
return false;
}
};
自己写的时候想的几个点:
- 首先原始函数入参只有数组和目标,直接递归的话,需要分割新的数组占用空间。所以直接新建函数,使用下标检索递归。
- 注意nums.size()的值比最大数组下标大一,所以搜索数组时
high =nums.size()-1
。 - 在递归中:
- 首先写出退出的情况,如果low比high还大,那么说明数组中没有找到目标,直接返回-1。
- 在求每次的二分中点的时候,想到卡哥说的防止超过int范围,所有使用
low+(high-low)/2
的方法,
- 接下来就是二分法比较中点数值时的三种情况(数组升序):关键是在递归寻找两边的数组时,注意开闭区间。在左边的时候需要把右边的中值去掉选择[low,mid-1],在右边是选择去掉左边的中值[mid+1,high]。也是一种左闭右闭吧
- 最后按照leetcode的要求,每个循环最后都需要有返回值,所以最后随便加了一行
return false
。
代码随想录解法
好像都没有用递归,我自己写的递归占用空间比较多
使用左闭右闭区间搜索
定义target在左闭右闭这个区间,[left, right]
几个关键点:
- 首先循环条件定为
while (left <= right)
,因为左闭右闭,所以左=右是有意义的 if (nums[middle] > target) right = middle - 1
和我写的一个意思,因为左闭右闭,需要去掉比较过的mid值
具体代码如下:
// 版本一
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
- 时间复杂度O(log n): 二分法循环的区间量级,n,n/2,n/4,…,n/2k(接下来操作元素的剩余个数),其中k就是循环的次数。 n/2^k=1.可得k=log2n(2为底)。所以复杂度为O(log n)
- 空间复杂度:一直使用一个数组的常数空间所以O(n)
使用左闭右开搜索
定义target在左闭右闭这个区间,[left, right)
几个关键点:
- 首先循环条件定为
while (left < right)
,因为左闭右开,所以比较右边界是没有意义的。 if (nums[middle] > target) right = middle
因为当前nums[mid]
不等于目标,去左边区间寻找,因为时左闭右开,可以将比较过的mid写进区间中,下一次也不会在对他进行比较
具体代码如下:
// 版本二
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
- 时间复杂度O(log n)
- 空间复杂度O(n)
27.移除元素
想的是直接将数组尾部元素补到需要删除的元素位置,但写了半天没写出来
代码随想录答案-快慢指针
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;
}
};
其中快慢指针的定义:
- 快指针:寻找新数组的元素, 把原数组全部遍历一遍
- 慢指针:指向更新的新数组的下标位置
代码随想录答案-移动最少元素
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一定指向了最终数组末尾的下一个元素
}
};
使用左右两个指针,向中间逼近,我一开始也是这么想的,但是没写出来
使用while,结束条件为左指针超过右指针,说明所有元素都被遍历一遍
但是在每次判断时都要确保左指针没有超过右指针
- 首先移动左指针,寻找值为vla的元素,如果不等于val就一直移动
- 同理移动右指针,直到遇到值不等于val的元素,方便覆盖左指针找到的val
- 如果能走到if判断,并且右指针位置大于左指针,就使用右指针值覆盖左指针
- 最后返回的是左指针位置,代表左边数组都是不含有val的,此时就是数组元素个数
第一天结束