代码随想录算法训练营第一天 | 704. 二分查找,27. 移除元素
力扣704 二分查找
题目链接
题目描述
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
具体思路
二分查找板子题
写法一 闭区间版本二分查找
执行二分查找的代码,就会一直执行同样的流程, 因此需要分析清楚其中的循环不变量。
循环不变量就是保证区间定义的不变
在闭区间 [left, right] 版本的二分查找中,下面代码中(0)(1)(2)(3)处都需要按闭区间来处理
(1)当 left = right 时,[left, right] 仍有意义,故(1)处 while 的条件需要取等
(2)当 nums[middle] < target ,说明要去middle右侧区间 [middle + 1, right] 内查找 target;
故更新 left 为 middle + 1;
(3)当 nums[middle] > target ,说明要去middle左侧区间 [left, middle - 1] 内查找 target;
故更新 right 为 middle - 1;
C语言代码
int search(int* nums, int numsSize, int target) {
int length = numsSize;
//(0) 在闭区间[0, length-1]进行二分查找
int left = 0;
int right = length - 1;
while(left <= right){ // (1) 需要取等
int middle = left + (right - left) / 2; // 防止溢出
// target更大,在middle右侧区间[middle + 1, right]进行查找
if(nums[middle] < target){
left = middle + 1; // (2)
// target更小,在middle左侧区间[left, middle - 1]进行查找
}else if (nums[middle] > target){
right = middle - 1; // (3)
// 值相等,查找成功
}else{
return middle;
}
}
// 查找失败
return -1;
}
时间复杂度 o(logn)
写法二 开区间版本二分查找
开区间一般取左闭右开这种形式,左开右开,左开右闭都不常用
循环不变量是区间的定义左闭右开,因此同样要在整个流程中遵循相同的区间定义
(1)当 left = right 时,[left, right) 没有意义,故(1)处 while 的条件不取等
(2)当 nums[middle] < target ,说明要去middle右侧区间 [middle + 1, right) 内查找 target;
故更新 left 为 middle + 1;
(3)当 nums[middle] > target ,说明要去middle左侧区间 [left, middle) 内查找 target;
故更新 right 为 middle;
C语言代码
// 开区间版本
int search(int* nums, int numsSize, int target) {
int length = numsSize;
// (0) 在闭区间[0, length)进行二分查找
int left = 0;
int right = length;
while(left < right){ // (1) 不取等
int middle = left + (right - left) / 2;
// (2) target更大,在middle右侧区间[middle + 1, right)进行查找
if(nums[middle] < target){
left = middle + 1;
// (3) target更小,在middle左侧区间[left, middle)进行查找
}else if(nums[middle] > target){
right = middle;
}else{
return middle;
}
}
return -1;
}
时间复杂度 o(logn)
以上思路是看过随想录之后整理的自己的思路,随想录的内容请见下面链接~
力扣27 移除元素
题目链接
题目描述
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
解题思路
用 的时间复杂度实现 erase()的功能,具体使用的是双指针的思想。
暴力解法 C语言代码
// Time: O(n^2) Space:O(1)
int removeElement(int* nums, int numsSize, int val) {
int size = numsSize;
for(int i = 0; i < size; i++){
if(nums[i] == val){ // 发现需要移除的元素,就将数组集体向前移动一位
for(int j = i + 1; j < length; j++){
nums[j - 1] = nums[j];
}
// 易错点: nums[i]被覆盖后,要重新判断nuns[i]与val的大小关系
// 而随后会有i++,故先减去1
i--;
// 数组大小也-1
size --;
}
}
return size;
}
双指针算法(核心解法) C语言代码
算法思路:
通过一个 fast 指针和一个 slow 指针在一个 for 循环下完成两个 for 循环(暴力)下的工作
fast 用于寻找新数组的元素(即不等于 val 的元素),新数组中不含等于 val 的元素
slow 指向新数组下标的更新位置,元素插入新数组的存放位置
// Time:O(n) Space:O(1)
int removeElement(int* nums, int numsSize, int val) {
int slow = 0;
for(int fast = 0; fast < numsSize; fast++){
if(nums[fast] != val){ // 遇到等于val的元素,fast就跳过
nums[slow] = nums[fast];
slow ++;
}
}
return slow;
}
注意:这里元素的顺序相较于原数组并没有发生变化,执行这种同向的双指针算法,只要原来数组中存在等于 val 的元素,就需要把后面所有的元素挪动。
而题目允许数组内元素的相对顺序可以发生变化,因此可以用相向的双指针算法进行优化,减少元素的移动次数。
相向的双指针算法(优化解法) C语言代码
// Time:O(n) Space:O(1)
int removeElement(int* nums, int numsSize, int val) {
int left = 0;
int right = numsSize - 1;
while(left <= right){
// 务必取等 这样结束时right=left-1,保证不会漏掉对交汇点的分析
// 取等是为了right和left可以相交汇,left走过的左侧均不包含等于val的元素
// 同时此时left指向是新数组所有元素的下一个位置,下标值就是新数组的大小
// 左边寻找等于val的元素,等待覆盖
while(left<=right && nums[left] != val) left++;
// 右边寻找不等于val的元素
while(left<=right && nums[right] == val) right--;
// 将右边不等于val的元素覆盖左边等于val的元素
if(left <= right) nums[left ++] = nums[right --];
// 由于只是用right指向的元素覆盖left指向的元素(而不是交换元素),所以right右侧
// 仍可能有不等于val的元素,但其副本已经全部覆盖到左侧
}
// left一定指向了最终数组末尾的下一个元素
return left;
}
以上思路是看过随想录之后整理的自己的思路,随想录的内容请见下面链接~