代码随想录算法训练营第一天 | 704. 二分查找,27. 移除元素

69 阅读5分钟

代码随想录算法训练营第一天 | 704. 二分查找,27. 移除元素

力扣704 二分查找

题目链接

力扣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;

故更新 leftmiddle + 1;

(3)当 nums[middle] > target ,说明要去middle左侧区间 [left, middle - 1] 内查找 target;

故更新 rightmiddle - 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;

故更新 leftmiddle + 1;

(3)当 nums[middle] > target ,说明要去middle左侧区间 [left, middle) 内查找 target;

故更新 rightmiddle;

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)

以上思路是看过随想录之后整理的自己的思路,随想录的内容请见下面链接~

随想录题解文章链接 力扣704

随想录题解视频讲解 力扣704

力扣27 移除元素

题目链接

力扣27 移除元素

题目描述

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1)O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

解题思路

O(n)O(n) 的时间复杂度实现 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,保证不会漏掉对交汇点的分析
        // 取等是为了rightleft可以相交汇,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;
}

以上思路是看过随想录之后整理的自己的思路,随想录的内容请见下面链接~

随想录文章链接 力扣27

随想录视频链接 力扣27