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

题目链接:leetcode 704、二分查找

文章讲解:代码随想录 704、二分查找讲解

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

自己看到题目的第一想法

题目: 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
提示: 你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
想法: 这个是比较经典的问题,因为数组已经排好序,所以二分查找应该是效率较高的查找算法。
基本过程如下:1、获取数组元素个数 n n n 2、在二分过程中需要有三个指针(下标,不一定是C++语言的指针),一个指向当前查找区间的左端点(例如开始时为0),一个指向查找区间的右端点(开始时为n-1),最后一个指向查找区间的二分位置,用于对比二分位置的元素和目标值 3、对比二分位置元素和目标值,如果当前值>目标值,说明目标值在当前值左侧,更新查找区间右侧指针为二分位置,反之更新查找区间左侧指针。

结束查找有两种情况:1、二分位置元素就是目标值,返回当前二分位置下标即可 2、没有查找到目标值,这个涉及到左右指针具体的意义,这里选取左右指针初始值分别为 0 , n − 1 0,n-1 0n1的情况,那么也就是说左右指针是在查找区间范围内的,可能是需要被访问到的,不能排除掉,那么在更新左右指针时就更新到二分位置减1或者加1的位置,因为如果二分位置不是目标值,那么是已经可以排除在查找区间外的,同时查找区间是左右端都包含的,也就是左右指针可能相等,这个时候二分位置就是左右指针的位置,对比如果不等于目标值,再次更新左右指针时,左指针会大于右指针,这时候返回-1代表没有查找到目标值。

其他细节补充:二分位置的计算不需要考虑太多,(左指针+右指针)/2即可。算法时间复杂度应该是 l o g ( n ) log(n) log(n),时间复杂度计算过程如下:一次二分查找需要对比中间指针对应元素和目标值,修改一次左指针或右指针,都是常数级别操作,假设k次二分找到了目标值,第一次在 n n n个元素内查找,第二次在 n / 2 n/2 n/2个元素内查找,以此类推,最坏的情况就是最后只剩一个元素才刚好查找到目标值,此时就是第k次查找,只剩一个元素, n / 2 k = 1 n/2^k = 1 n/2k=1,则 k = l o g 2 n k = log_2 n k=log2n,所以时间复杂度为 O ( l o g n ) O(logn) O(logn)级别。

自己写的代码:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n - 1;
        int m;
        while(l <= r) {
            m=(l + r) / 2;
            //找到了
            if(nums[m] == target) return m;
            //如果小于target,说明target在右区间,更新左指针
            if(nums[m] < target) {
                l = m + 1;
            }else {
                r = m - 1;
            }
        }
        //查找失败
        return -1;
    }
};

顺利通过了

看完代码随想录和大家探讨之后的想法

1、空间复杂度忘记计算了,因为只用了3个指针,所以是 O ( 1 ) O(1) O(1)
2、我写的对应文章讲解的左闭右闭区间的讲解
3、取中间下标的计算可以用右移一位代替,计算效率更高,平常看到的代码有人确实会直接这样写, 编译器也会把除以2优化为右移一位,这个看具体编译器的情况

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n - 1;
        int m;
        while(l <= r) {
            m=(l + r) >> 1;
            //找到了
            if(nums[m] == target) return m;
            //如果小于target,说明target在右区间,更新左指针
            if(nums[m] < target) {
                l = m + 1;
            }else {
                r = m - 1;
            }
        }
        //查找失败
        return -1;
    }
};

我对右移这个知识点不熟悉,应该还有要注意的点,应该是只有整数才可以这样做

题目链接:leetcode 27、移除元素

文章讲解:代码随想录 27、移除元素讲解

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

自己看到题目的第一想法

题目: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
想法: 要求原地移除,且不需要考虑数组中超出新长度后面的元素,那么就遍历数组,把等于val的值通过直接覆盖移除掉即可,要覆盖就要同时用两个指针(下标),就是双指针法,两个指针同时从开头出发,不等于val的值两指针都向右移动,等于val时,一个停留在原地等待被覆盖,另一个指针继续移动,然后赋值。

考虑一些特殊情况,假设有多个连续的val值存在,慢指针动,快指针移动直至不等于val时再进行覆盖,覆盖以后慢指针和快指针均移动一次,然后继续判断。这样想的话,不管是进行覆盖操作还是双指针一起后移操作,每种情况快指针都要移动,那么循环就以快指针为条件,快指针遍历整个数组,在循环中如果慢指针不等于val值,慢指针移动一次,如果慢指针等于val值,慢指针就不动,快指针移动,那么什么时候覆盖呢?前面提到快指针不等于val值时才覆盖,所以每次循环开头先判断一次快指针是否等于val以及慢指针是否等于val,只有慢指针等于val,快指针不等于val时才进行一次覆盖,然后此时慢指针不等于val,再判断慢指针是否等于val自然让慢指针后移。

第二个问题是考虑一个特殊情况,如果数组开头就是val会怎么样,推算后不需要特殊处理步骤。

第三个问题是什么时候结束循环,是否可以提前结束循环,不可以提前结束,快指针遍历完以后才能确定所有val都被删除了,所以还是要循环遍历完。

由于慢指针每次覆盖后一定会后移,所以移除后数组的新长度就是慢指针。

综上所述,循环以快指针为条件进行,快指针一直右移,循环中进行以下操作:
1、如果慢指针此时为val,快指针不为val,进行一次覆盖操作。
2、判断当前慢指针是否为val,如果不是val,慢指针右移,如果是val,慢指针不动,等待被覆盖,通过循环右指针会不断右移,满足条件后就会进行覆盖操
作,慢指针就可以移动了。

写出来代码出现了bug,发现错误在于少考虑了一种情况,如果慢指针为val,快指针也为val,那么应该跳过本次循环。

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

然后又发现条件判断可以整合如下:

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

还是有bug,看讲解去了。

看完代码随想录和大家探讨之后的想法

思路没什么大问题

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};

对比自己的代码和这个代码,区别在于我考虑了更多的情况,因为我自己思考的时候觉得如果慢指针不等于val值,就没必要进行覆盖操作,会浪费时间。

但是我忽略了一个问题,当出现一次val值时,进行一次覆盖后,后面每次其实都需要不停覆盖,所以没必要想那么多,仅通过快指针决定是否覆盖就可以,慢指针仅在覆盖后才需要右移。这样思路清晰很多。

第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值