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

本文详细介绍了二分查找算法在LeetCode问题704中的应用,包括解题思路、代码实现和提交结果,以及移除元素问题27的解题策略,重点强调了双指针优化方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

704. 二分查找

一、题目描述

二、解题思路

三、解题代码

四、提交结果

五、解题感想

27.移除元素

一、题目描述

二、解题思路

三、解题代码

四、提交结果

五、解题感想


704. 二分查找

题目:704.二分查找

文档:代码随想录

视频:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找

一、题目描述

        给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1


示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释:9出现在nums中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2不存在nums中因此返回 -1

二、解题思路

        比较基础简单的题目,利用二分算法即可。

        首先设置left和right两个整型变量来记录数组的首尾。由于给出的是vector数组,所以数组开头位置下标为0,数组结尾是内置函数size得出数组的大小并-1(数组下标从零开始计算,所以得出的size大小要-1才是指向最后一个数组)。此时的指向是这样的:

        找到数组中间的位置,也就是mid=(left+right)/2,如果此时mid指向的数字等于我们要查找的数字,返回mid即可。

        如果mid指向的数字大于我们要查找的数字,由于数组是升序排序,说明我们要找的数字在从左边起到mid-1这个范围中,这时候令right=mid,继续执行二分查找。

        如果mid指向的数字小于于我们要查找的数字,由于数组是升序排序,说明我们要找的数字在从mid+1起到右边指向数字这个范围中,这时候令left=mid,继续执行二分查找。

        如果此时mid指向的数字不等于我们要查找的数字,left和right有一方更新了所指向的数组的位置,也就是查找范围减半,此时再在新的查找范围里面继续二分查找,直到找到所要数字返回为止。如果更新到righ<left,就说明遍历了数组后还是没有找到目标数字,此时跳出循环,返回-1。

三、解题代码

//非递归方法
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int right = nums.size()-1;//数组结尾位置(初始)
        int left = 0; //数组开头位置(初始)
        while(left<=right){//如果数组一开始就为空的话会有left>right
            int mid = (left+right)/2;//二分位置
            if(nums[mid]>target){
                right = mid-1;//中间位置比目标数大,更新右边
            }
            else if(nums[mid]<target){
                left = mid+1;//中间位置比目标数大,更新左边
            }
            else if(nums[mid]==target){
                return mid;
            }

        }
    return -1;
    }
};

四、提交结果

五、解题感想

        二分查找算法算很基础,也很容易理解的算法,做起来不难。不过一开始确实没注意到左闭右开、左闭右闭、左开右闭的说法。一般用的是左闭右开,可能是担心内存泄漏等问题?总之二分法就是这么个思想啦。

27.移除元素

题目:27.移除元素

文档:代码随想录

视频:数组中移除元素并不容易! | LeetCode:27. 移除元素

一、题目描述

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

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

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

说明:

        为什么返回数值是整数,但输出的答案是数组呢?请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5并且 nums 中的前五个元素为 0,1,3,0,4注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

二、解题思路

        很久没接触双指针的题了,一开始看到是vector容器第一反应是用erase,确实也能用erase,或者暴力双循环解法,但其实没必要,因为输出结果的元素可以为任意顺序,而且不需要考虑超出新长度后面的数组,所以可以用双指针法,时间复杂度更小(算法,能省则省啦……)。

        这道题目我采用的是优化版的双指针,优化版的双指针在最坏情况下只需要遍历一遍数组,而普通双指针在最坏情况下要遍历两遍数组。

        首先设置左指针left = 0指向数组头,设置右指针right = nums.size()准备指向数组尾。

        不设置right = nums.size()-1是因为接下来的循环判断条件是left != right,如果数组长度为0就会陷入死循环。如果循环判断条件改成left <= right的话可将right初始化为right = nums.size()-1,时间和空间消耗也差不多。

        以left != right为判断条件,接下来进入循环:

        如果left指向的数字等于val,则需要删除,因为这里的要求是输出结果的元素可以为任意顺序,而且不需要考虑超出新长度后面的数组,所以这时候我们用右指针right-1指向的数字覆盖掉left指向的数字,执行操作nums[left]=nums[right-1]。这时候left不需要移动,因为我们不知道nums[right-1]是否等于val,需要等待下一次循环进行判断,而由于nums[right-1]已经使用过去覆盖数组了,我们将right往前移一格指向还没指向的数字,也就是执行操作right--

         如果left指向的数字不等于val,这个数就不需要进行处理,跳过就行,left往后移一格,但此时right不能移动,因为还没有使用它所指向的数字。

        当leftright重合的时候,也就把数组内所有的数字判断了一遍,将所有有val的值都用非val的值覆盖了,此时left的大小就是数组长度大小。

        至于后面right使用并跳过的数字,都用于覆盖left前有val值的数,所以left指向的数及其之前的数就是删掉val后还剩下的数,至于结束时候left指向的数之后的数是不会进行输出的,无需处理。


        记录一下双指针的解法。未优化版的双指针是快慢指针之分。设fast和slow分别为快指针和慢指针,初始化的时候都为0(指向数组开头)。快指针是无论有没有遇到val都继续向后探索的,而慢指针是遇到val后停下来,等快指针指到其之后最近的一个非val值后,将其覆盖,然后慢指针向后移一格。快指针遍历结束后,慢指针大小就是新数组的长度。

三、解题代码

用优化双指针的代码

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int left = 0, right = nums.size();
        while(left != right){
            if(nums[left] == val){
                nums[left] = nums[right-1];
                right--;
            }
            else{
                left++;
            }
        }
        /*如果循环判断条件为left<=right的话可以写成
         *  int left = 0, right = nums.size()-1;
         *  while(left <= right){
         *    if(nums[left] == val){
         *        nums[left] = nums[right];
         *        right--;
         *    }
         *    else{
         *        left++;
         *    }
         *  }
         */
        return left;
    }
};

用双指针的代码

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];
                slow++;
            }
        }
        return slow;
    }
};

用vector内置函数erase(pos)的代码

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        for(int i = 0; i < nums.size(); i++){
            if(nums[i]==val){
                nums.erase(nums.begin()+i);
                i--;//删掉了其后所有元素都往前移动了一格,记得把i-1.
            }
        }
        return nums.size();
    }
};

四、提交结果

用优化双指针的提交结果

用双指针的提交结果

用vector内置函数erase(pos)的提交结果

五、解题感想

        复习了一下双指针的用法。这道题能用双指针还是得因为输出结果的元素可以为任意顺序,而且不需要考虑超出新长度后面的数组。今天的两道题总体还是挺简单的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值