代码随想录算法训练营第1天 | 704.二分查找、27.去除元素
文章目录
704.二分查找
💡解题思路
由于数组元素为有序排列,我们仅需要确定搜索的左右边界。在该题中存在两种情况,即右边界的选择:
right = nums.size() - 1;
在这种情况下,每次的搜索区间为 [left, right] 即左闭右闭区间,此时对应的循环条件应为 while (left <= right)
,终止条件为 left == right + 1
,即 [right + 1, right],此时区间为空,故循环终止,程序返回 -1 即可;
right = nums.size();
对于这种情况,由于数组,此时的搜索区间为[left, right)即左闭右开区间,此时对应的循环条件为 while (left < right)
,终止条件为 left == right
,即 [right, right],此时区间内仅存一个元素 right,直接返回 -1 是不对的,还需对该索引进行判断。
// ...
while (left < right)
{
// ...
}
return nums[left] == target ? left : -1;
🤔遇到的问题
- 未考虑到 left + right 可能会溢出的问题;
- 在使用
>>
运算符时忘记加()
对运算顺序进行约束; - 循环的结束条件这里卡了一会儿。
💻实现代码
左闭右闭区间
class Solution
{
public:
int search(vector<int> &nums, int target)
{
int l = 0, r = nums.size() - 1;
while (l <= r)
{
int m = l + (r - l) / 2;
if (target == nums[m])
{
return m;
}
else if (target < nums[m])
{
// 此时搜索区间为 [l, m - 1]
r = m - 1;
}
else if (target > nums[m])
{
// 此时搜索区间为 [m + 1, r]
l = m + 1;
}
}
// 此时 l > r,未找到目标数
return -1;
}
};
左闭右开区间
class Solution
{
public:
int search(vector<int> &nums, int target)
{
// right 数组下标越界
int l = 0, r = nums.size();
while (l < r) // 注意 循环条件为 l < r,循环终止时 l == r
{
int m = l + (r - l) / 2;
if (target == nums[m])
{
return m;
}
else if (target < nums[m])
{
// 此时搜索区间为 [l, m)
r = m;
}
else if (target > nums[m])
{
// 此时搜索区间为 [m + 1, r)
l = m + 1;
}
}
// 此时 l == r,还需进行再次判断
return nums[l] == target ? l : -1;
}
};
🎯题目总结
解题耗时:28 mins 45 secs
作为二分查找中最基础的一种类型,即查找某一个数,处理好搜索区间即可快速解决该问题。
左闭右闭
这种情况下搜索区间为 [left, right],
当 target < nums[m]
时(即 target 在左区间时),对应的搜索区间为 [left, mid - 1],因为 mid 已经搜索过了,所以我们只需在 mid 左侧的闭区间内进行搜索;
当 target > nums[m]
时(即 target 在右区间时),对应的搜索区间为 [mid + 1, right],解释同上;
当循环结束时,left == right + 1
,未找到目标,返回 -1。
左闭右开
这种情况下搜索区间为 [left, right),
当 target < nums[m]
时(即 target 在左区间时),对应的搜索区间为 [left, mid),因为右边界为开,不会被搜索,故我们只需让 right = mid
即可;
当 target > nums[m]
时(即 target 在右区间时),对应的搜索区间为 [mid + 1, right),因为左边界为闭,而 mid 已被搜索过,故我们需要让 left = mid + 1
;
当循环结束时,left == right
,我们需要判断当前下标对应的数组元素是否为目标元素,若是则返回下标,否则返回 -1。
27.去除元素
💡解题思路
由于题目要求原地修改,则我们不能重新 new
一个新的数组,而是只能在原数组上进行操作,最后返回一个长度。
暴力解法
暴力方法的思路就不多说了,即通过嵌套循环将数组内为 val 的元素覆盖,由于涉及到数组移动的操作,故时间复杂度较高。
时间复杂度: O ( N 2 ) O(N^2) O(N2) 空间复杂度: O ( 1 ) O(1) O(1)
双指针解法
使用快慢指针对数组进行遍历,如果 fast
指针遇到值为 val
的元素,直接跳过,等待 slow
指针将其覆盖,从而达到删除元素的效果;如果 fast
指针遇到值不等于 val
的元素,将其值赋值给 slow
指针,然后让 slow
指针前进一步。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NO7HhYaU-1675243333432)(D:\Files\Vaults\images\快慢指针.gif)]
注意这里是先给 nums[slow]
赋值然后再给 slow++
,这样可以保证 nums[0..slow-1]
是不包含值为 val
的元素的,最后的结果数组长度就是 slow
。
时间复杂度: O ( N ) O(N) O(N) 空间复杂度: O ( 1 ) O(1) O(1)
🤔遇到的问题
- 暴力法时外层循环结束条件误写为
i < nums.size()
导致超时 - 双指针算法快指针移动的条件想了一会儿才想通为这么这样做(🐷🧠)
💻实现代码
暴力解法
class Solution
{
public:
int removeElement(vector<int>& nums, int val)
{
if (nums.size() == 0) return 0;
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--;
// 数组长度改变
size--;
}
}
return size;
}
};
双指针解法
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
if (nums.size() == 0) return 0;
int fast = 0, slow = 0;
while (fast < nums.size())
{
// fast 指针遇到等于 val 的元素跳过
// 等待 slow 指针将这些元素覆盖
// 即达到删去 val 的目的
if (nums[fast] != val)
{
// 先覆盖,再对 slow 自增,保证 nums[0...slow-1] 中不包含 val
nums[slow] = nums[fast];
slow++;
}
fast++;
}
// nums[0...slow-1] 的长度即为 slow
return slow;
}
};
🎯题目总结
解题耗时:18 mins 32 secs
在处理数组和链表相关问题时,双指针技巧是经常用到的,双指针技巧主要分为两类:左右指针和快慢指针。本题使用到了快慢指针。
🎈今日心得
今天的两道题在远古之前我刷过,但是都没什么印象了。每次开始刷题就坚持个一两天就歇逼了,希望这次可以坚持下来。其实解题时间并不长,但是写博客真的费时间…加油加油💪
这里是咸小淳,一条想要努力的小咸鱼🐟