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

704.二分查找


文档讲解:代码随想录

视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili

状态:做出来了

//算法和程序设计技术的先驱高德纳曾说过:虽然二分查找的基本思想相对简单,但细节可能会非常棘手。


第一次接触二分查找的代码是b站上的一个教程视频:二分查找为什么总是写错?_哔哩哔哩_bilibili

这个视频将二分查找的初始边界定为了-1和N(并非N-1),做法是根据题目确定一个边界条件,将给定的数组分成红蓝两个边界,然后根据具体情况取红边或者蓝边的某个值。

循环条件:while(l+1!=r)

为何初始边界是-1和N:

该做法意为将未涂色的数组涂上红蓝两色来划分,l==-1代表l所在字符以及之前的字符都是蓝色,r==N表示r所在字符以及之后字符都为红色。如果初始边界为0或者N-1的话,r==0意为该字符为蓝色,但是很有可能整个数组都是红色的,便出现错误,r==N-1同理。

该做法的指针更新:

这种写法更新指针时,能不能写成l=m+1r=m-1呢? 并不行:

假如m为蓝色边界的最后一个,令其+1便会使蓝色指针跳到红色区域,反之同理。

 如图:

注意边界条件 :

l或者r可能一直在初始位置(不在数组内),即整个字符串为全红或者全蓝,这时候要考虑l和r不在数组内的边界条件


接下来是学习随想录的做法: 

该做法与上述做法在mid的判断上有区别,上述做法进行两次判断,循环中只将数组分为两个部分,在循环外根据条件返回值。而随想录在循环中进行三次判断,如果直接找到target就返回,不然缩小范围,缩到最后还找不到就return-1;

个人认为上一种做法在类似题目上更具有普适性,如在寻找第一个不大于或者不小于的数字,可作为一种模板。

随想录做法分为两种,左闭右闭和左闭右开(左开右闭)

左闭右闭:

此时的初始条件:l=0,r=num.size()-1

即left和right都包含在区间里面,此时的循环条件应为while(left<=right),因为left==right的比较是有意义的。

在判断mid时,if (nums[mid] > target)  ,将r取作mid-1,因为比较区域中是包括r的,所以要把不符合的mid排除掉。

循环结束条件:l的更新在mid前一位,r更新在mid后一位,多次更新后r会在l的左边此时循环结束(r==l时依然没找到target,r和l的左右次序就变了)

左闭右开:

此时初始条件:l=0,r=num.size()

right不被包含在区间里,所以循环条件为while(left<right),因为left不能跟不在区间里的right比较。

在判断mid时,if(nums[mid]>target),将r取作mid,将mid排除在区间外。

 



 

27.移除元素


文档讲解:代码随想录

视频讲解:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili

状态:做出来了


第一次看到这个题目的时候心里想的是一版数据结构的书上对于vector容器的删除操作

如图:

 

第一次看书上这个做法的时候觉得很巧妙,结果竟然是暴力做法(无语了)。 

后来想到图上这种做法其实是要保持相对次序,本题题目要求为元素顺序可以改变,于是只交换应被删除元素和数组尾

代码如下(这个代码块怎么不能调节大小,看着好难受) :

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int l=0;
        int r=nums.size()-1;
        while(l<=r)
        {
            if(nums[l]==val)
            {
                nums[l]=nums[r];
                r--;
            }
            else
            l++;
        }
        return r+1;
    }
};

边界条件:如果l==r时候仍要被删除,r=-1,即数组为空。此时返回r+1=0个元素 。

该代码思路和leetcode题解给出的思路基本一致,只遍历了一遍数组,复杂度自然为O(n)。

但是怎么在O(n)复杂度下保持元素顺序呢?


学习随想录的算法:

我以为上述做法已经够巧妙了,没想到还有更勇猛的,这是谁的部将。

快慢指针算法:

让快指针和慢指针都指向初始位置,如果快指针指向的位置不等于val,就将快指针的值赋给慢指针所在位置,如果等于val,快指针就向前走一位。

当快指针走到最后时,循环结束,此时快指针将整个数组遍历了一遍,并将所有不等于val的值依次赋给了原数组形成新数组,复杂度为O(n),且保持了元素顺序。

代码:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int l=0;
        for(int r=0;r<nums.size();r++)
        {
            if(nums[r]!=val)
            nums[l++]=nums[r];
        }
        return l;
    }
};



 

总结:

这两道题之前都学习过 ,所以写起来比较快,今天的时间比较充裕,所以博客写的详细一些,长一些。之后如果忙起来会尽可能写个大纲找机会补上。

写博客感觉好处还是很多的,之前脑袋里有很多想法,知道是怎么回事,落到文字上才发现有些说不清道不明的地方,将其转化为文字有助于对我自己思考的整理。

就先这样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值