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

704.二分查找

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

示例一:

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

示例二:

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

提示:

1、你可以假设 nums 中的所有元素是不重复的。
2、n 将在 [1, 10000]之间。
3、nums 的每个元素都将在 [-9999, 9999]之间。

解题思路:

  1. 关键词提取:整型数组、数组有序、目标值、返回下标、假定元素不重复
  2. 暴力解法:一次遍历,判断条件为==target,时间复杂度是o(n)
  3. 二分法:一次遍历,双指针,不断将数组区间对折,时间复杂度是o(nlogn)
    1. 左右指针的取值,左闭右闭,左指针取下标0,右指针取下标size-1
    2. 中值的取值,左右指针之和再除2,换一种写法避免数据类型溢出
    3. 循环的判断条件,左指针小于或者等于右指针,原因是,左闭右闭的取法,假如最终剩下2个元素,再次对折,就是1个元素,刚好左右指针指向同一个元素
    4. 中值与目标值的判断之后,左指针和右指针的取值难点,+1和-1的原因。第一次取中值:[nLeft, nMid]和[nMid, nRight]。挖掉中间的nMid值以后,两个区间未检索的值如下:在左区间[nLeft, nMid - 1] 或者 在右区间[nMid + 1, nRight],因此,左指针为nMid + 1, 右指针为nMid - 1

二分法代码如下:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int nLeft = 0;
        int nRight = nums.size() - 1;
        int nMid = 0;
        // 在数组中采用二分法找对应的元素
        // 二分法的区间:左闭右闭,[nLeft, nRight]区间内的元素都在选择中
        while(nLeft <= nRight)
        {
            // 每次循环,更新中值的元素下标
            // 这种写法可以避免nMid出现整型溢出
            nMid = nLeft + (nRight - nLeft) / 2;
            // 中值偏小,需要在右区间再次检索
            if(nums[nMid] < target)
            {
                nLeft = nMid + 1;
            }
            // 中值偏大,需要在左区间再次检索
            else if(nums[nMid] > target)
            {
                nRight = nMid - 1;
            }
            // 找到对应的值,返回元素下标
            // nums[nMid] == target
            else
            {
                return nMid;
            }
        }
        // 找不到对应的元素
        return -1;
    }
};

总结:

  1. 第一次做的时候,仅仅会暴力解法,而且不知道什么是二分法。
    1)看到题解写着二分法,标出左右指针以及中值的计算,仅仅是记住代码模板,往里生搬硬套。
    2)后续做题目的时候,仅仅知道检索一个元素特定值,可以采用这种方式,并不知道优势在哪,也不知道有哪些应用条件限制以及如何去应用。
  2. 跟着代码随想录刷题的时候,才发现二分查找法是有限制的,而且是有着易错点的。
    1)是我忽略了区间选择中,左闭右闭和左闭右开的情况,默认都选择左闭右闭。
    2)是我忽略了区间缩小时,左右指针重新赋值的依据是左闭右闭的区间选择。
    3)假如未排序,那么和暴力解法是一样的时间复杂度。
    4)假如有重复元素,会出现多个解。

27.移除元素

题目描述:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(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],也会被视作正确答案。

示例二:

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

提示:

0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100

解题思路:

  1. 关键词提取:数组、目标值、原地移除、新长度
  2. 暴力法:两次遍历,第一次遍历负责检索目标值,第二次遍历负责删除目标元素,时间复杂度o(n^2)
  3. 双指针法:一次遍历,双指针,一个指针负责遍历数组,一个指针负责新数组的元素赋值
    1. 将一个数组复制为两个数组,一个指针在遍历上方数组[3,3,2,2]
    2. 一个指针在遍历下方数组[3,3,2,2],从下标0开始,检索到3时,不赋值,不移动,检索到2时,先将下标0元素赋值为2,新数组为[2,3,2,2],再移动到下标1,再次检索到2,将下标1元素赋值为2,新数组为[2,2,2,2],再移动到下标2
    3. 跟随着遍历结束,指针停留在下标2位置,新数组为[2,2],长度跟指针指向的下标位置2一致,直接返回
      在这里插入图片描述
      双指针法代码如下:
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        // 修改原数组,不使用额外数组空间
        // 定义一个新的指针,指向数组第一个元素
        // 该指针指向的位置,用于保存需要记录的元素
        int nNewIndex = 0;
        // 开始遍历数组,从第一个元素开始
        for(int i = 0; i < nums.size(); i++)
        {
            // 假如该元素不是目标元素,就按照顺序从第一个元素开始,存放起来
            if(nums[i] != val)
            {
                // 在当前位置,保存需要记录的值(可能是当前位置后的第三个或者第五个),会覆盖当前位置的值
                nums[nNewIndex] = nums[i];
                // 指针右移一位,等待下一个需要记录的值
                nNewIndex++;
            }
        }
        // 返回需要记录的元素的长度
        return nNewIndex;
    }
};

总结:

  1. 第一次做的时候,仅仅会暴力解法,而且不知道什么是双指针法。
    1)看到题解的时候,才发现,原来可以采用这种双指针的方式,一个循环做两个循环的事情。
    2)掌握的思路是,只要新增一个变量,两重循环转换为一重循环,做复杂度降维,那么,是否还有三指针或者四指针法,一个循环将三个循环的事情都做了,后续的三数之和刚好可以对得上这个思考。
  2. 跟着代码随想录刷题的时候,才发现一道题是有很多的解题思路的,而且有很多的细节要处理。
    1)假如只是为了做出题目,那么暴力解法已经完成这个目标,但是要提升自己的话,还是需要再看看别人的思路,多角度去思考,举一反三,才算是掌握。
    2)在不改变元素的相对位置的情况下,暴力解法和双指针法,都可以解出该题,但是,假如改变相对位置,会怎样呢。我的想法是,可以先做一遍排序,再去找到目标值的左右区间,然后一次性将后续的元素填充好。二分查找法是否可以优化至o(nlogn)。
    3)在不改变元素的相对位置的情况下,优化暴力解法,是否可以一次遍历,找到目标值的指定元素,切割出左右区间,再去精准的移动后续元素,这样子可以少一部份的元素移动次数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值