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

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

一.题目链接–704

二分查找
力扣题目链接:704.二分查找

代码随想录704链接

二.解题思路

当该数组为有序无重复数字时,进行查找只需要确定搜索范围即左右边界,然后不断缩小范围。
左边界永远不变,只需要判断右边界

2.1 右边界为right = nums.length - 1; 左闭右闭[left, right]

2.2 右边界为right = nums.length; 左闭右开[left, right)

注意边界的作用是为了让后面的left和right的取值符合区间定义,比如[1,1] 和[1,1)

三.遇到问题和注意事项

  • 3.1 注意先判断当值不在数组中,即超出的值需要如何处理,此处我们设置当值小于最小和大于最大时候就令返回值为1
  • 3.2 取中间值时候,为了避免int的数值过大导致溢出问题,所以用小值加上差值,再用二进制向右移一位的方法 left + ((right - left) >> 1

四.实现代码

4.1 左闭右闭

 //方法一:左闭右闭区间
    public int search(int[] nums, int target) {
          if (target < nums[0] || target > nums[nums.length - 1]){
            return -1;
        }
        int left = 0, right = nums.length - 1;
        while(left <= right){
            int middle = left + ((right - left) >> 1);
            if (target == nums[middle]){
                return middle;
            }
            //目标值在中间值的左边,因为左闭右闭,左边不动,
            //右边的值能取到,但是上一步已经排除相等,所以右边要减一
            else if (target < nums[middle]){
                right = middle - 1;
            }
            //目标值在中间值的右边,因为左闭右闭,右边不动,
            //左边的值能取到,但是上一步已经排除相等,所以左边要加一
            else if(target > nums[middle]){
                left = middle + 1;
            }
        }
        return -1;

    }

4.2 左闭右开

  //方法二:左闭右开区间
     public int search(int[] nums, int target) {
         if (target < nums[0] || target > nums[nums.length - 1]){
             return -1;
         }
         int left = 0, right = nums.length;
         while(left < right) {
             int middle = left + ((right - left) >> 1);
             if(target == nums[middle]){
                 return middle;
             }
             // 目标值在中间值的左边,因为左闭右开,左边不动,
             //右边的值不能取到,所以可以设置右边的边界为middle
            else if(target < nums[middle]){
                right = middle;
             }
             // 目标值在中间值的右边,因为左闭右开,右边不动,
             // 左边的值能取到,但是上一步已经排除相等,所以左边要加一
             else if(target > nums[middle]){
                 left = middle + 1;
             }
         }
         return -1;
     }

五.随想录知识点

关于 二分查找

● 最重要的就是分类讨论好二分,二分看着好写边界 case 还是需要测试的哈
● 什么是区间不变量? 比如 区间取左闭右闭的话 那么每次区间二分 范围都是新区间的左闭右闭  后面做判断时  要一直基于这个左闭右闭的区间
● 其实区间定义成开或者闭都没有什么关系  只是要明确每次收缩范围后 范围内的元素是哪些  注意会不会漏掉边界就好
● 大家需要注意二分的几种情况 
  ○ 当l = 0, r = n的时候因为r这个值我们在数组中无法取到,while(l < r) 是正确写法
  ○ 当l = 0, r = n - 1的时候因为r这个值我们在数组中可以取到,while(l <= r) 是正确写法 主要看能不能取到这个值
● 二分法有多种写法,末尾是开区间闭区间都可以解出寻找单个元素和寻找边界的题目,只需要注意相应的是l < r还是l <= r,每次取mid还是取mid加减一即可。建议理解后背熟一套模板,不要搞混。
● 其实二分还有很多应用场景,有着许多变体,比如说查找第一个大于target的元素或者第一个满足条件的元素,都是一样的,根据是否满足题目的条件来缩小答案所在的区间,这个就是二分的本质。另外需要注意,二分的使用前提:有序数组
● 二分的最大优势是在于其时间复杂度是O(logn),因此看到有序数组都要第一时间反问自己是否可以使用二分。
● 关于二分mid溢出问题解答: 
  ○ mid = (l + r) / 2时,如果l + r 大于 INT_MAX(C++内,就是int整型的上限),那么就会产生溢出问题(int类型无法表示该数)
  ○ 所以写成 mid = l + (r - l) / 2或者 mid = l + ((r - l) >> 1) 可以避免溢出问题
● 对于二进制的正数来说,右移x位相当于除以2的x几次方,所以右移一位等于➗2,用位运算的好处是比直接相除的操作快

一.题目链接–27

在这里插入图片描述

力扣题目链接:27.移除元素

代码随想录27链接

二.解题思路

题目要求在原数组里修改 不能再new一个新的数组,最后返回一个长度。

2.1暴力解法

暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组
暴力解法的时间复杂度是O(n^2)

2.2双指针(快慢指针)

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
最差需要遍历两次数组

定义快慢指针

快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新 新数组下标的位置

也可以令快指针为right 慢指针为left
但是注意此时的right和left都是最左边开始的值

2.3双指针(相向双指针)

相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
最差需要遍历一次数组

将最右边的先找到不为value所需要的下标,
再从左边开始依次将最右边的赋值给最左边,同时最右边仍然在判断是否不为value

2.4双指针 (官方给的优化双指针,类似上面的双向双指针)

三.遇到问题和注意事项

注意在暴力解法里面的设置两层循环条件时,将i < nums.length 改为在外面设置一个参数size赋值为nums.length,再令 i < size ,因为每次两层循环都是调用nums.length,会导致超时,所以直接先在外面给长度赋值为一个变量就好了。

四.实现代码

4.1暴力解法

    //方法一:暴力解法
    // 时间复杂度:O(n^2)
    // 空间复杂度:O(1)
    public int removeElement(int[] nums, int val) {
        int size = nums.length;
        for(int i=0; i<size; i++){
            if(nums[i] == val){// 发现需要移除的元素,就将数组集体向前移动一位
                for(int j = i+1; j<size; j++){
                    nums[j-1] = nums[j];
                }
                i--;// 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--;// 此时数组的大小-1
            }
        }
        return size;
    }

4.2双指针(快慢指针)

//方法二:双指针法(快慢指针法)
    // 时间复杂度:O(n)
    // 空间复杂度:O(1)
    public int removeElement(int[] nums, int val) {
            int slowIndex = 0;
            int size = nums.length;
            for(int fastIndex = 0; fastIndex < size; fastIndex++){
                if(nums[fastIndex] != val){
                    nums[slowIndex] = nums[fastIndex];
                    slowIndex++;
                }
            }
        return slowIndex;
    }

4.3双指针(相向双指针)

    //方法三:相向双指针法
    public int removeElement(int[] nums, int val) {
            int left = 0;
            int right = nums.length - 1;
            while(right >= 0 && nums[right] == val) right--;//将right移到从右数第一个值不为val的位置
            while(left <= right){
                if(nums[left] == val){ //left位置的元素需要移除
                       将right位置的元素移到left(覆盖),right位置移除     
                    nums[left] = nums[right];
                    right--;
                }
                left++;
                while(right >= 0 && nums[right] == val) right--; //将right再次移到从右数第一个值不为val的位置
            }
            return left;
        }

4.4双指针 (官方给的优化双指针,类似上面的双向双指针)

   //官方的答案:双指针优化
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length;
        while (left < right) {
            if (nums[left] == val) {
                nums[left] = nums[right - 1];
                right--;
            } else {
                left++;
            }
        }
    return left;
    }

五.随想录知识点

关于 移除元素

● 快指针可以理解成在旧数组中找非目标元素,然后赋值给慢指针指向的新数组,虽然都指向一个数组
● 关于二分法和移除元素的共性思考
这两题之间有点类似的,他们都是在不断缩小 left 和 right 之间的距离,每次需要判断的都是 left 和 right 之间的数是否满足特定条件。对于「移除元素」这个写法本质上还可以理解为,我们拿 right 的元素也就是右边的元素,去填补 left 元素也就是左边的元素的坑,坑就是 left 从左到右遍历过程中遇到的需要删除的数,因为题目最后说超过数组长度的右边的数可以不用理,所以其实我们的视角是以 left 为主,这样想可能更直观一点。用填补的思想的话可能会修改元素相对位置,这个也是题目所允许的。
● fast < nums.size() 和 fast <= nums.size()-1 没什么区别,那为什么第二个会在空数组时报数组越界的错误?
vector的size()函数返回值是无符号整数,空数组时返回了0,再减个一会溢出

第二十二算法训练营主要涵盖了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、付费专栏及课程。

余额充值