数组part01 Leetcode704. 二分查找、27. 移除元素

文档讲解:代码随想录

 704 二分查找 

6aa84ded32ee4cff924b9e5b75393d2f.png

   刚开始的思路是遍历nums数组,比较值相等返回位置。这么做肯定是超时的,原因是并没有接触过二分查找,脑子里面没有这个概念。

 二分查找需要注意的点:

1.数组为有序数组;

2.数组中无重复元素

因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。

二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢? 

写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

class Solution {
    public int search(int[] nums, int target) {
        if(target < nums[0] || target > nums[nums.length -1]){
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int mid = left + ((right - left) >> 1);
            if(target < nums[mid]){
                right = mid - 1;
            }else if(target > nums[mid]){
                left = mid + 1;
            }else if(target == nums[mid]){
                return mid;
            }
        }
        return -1;
    }
}

结果:5f991d0dc24143d4950b023f1ba7b17a.png

 代码解释:

  1. 首先,这是一个名为Solution的类,其中包含了一个名为search的公共方法,该方法接受一个整数数组nums和一个目标整数target作为参数,返回目标整数在数组中的索引,如果不存在则返回-1。

  2. 在方法的开头,有一个条件判断:if (target < nums[0] || target > nums[nums.length - 1])。这个判断用于优化搜索过程。如果目标整数小于数组的第一个元素或者大于数组的最后一个元素,那么目标整数肯定不在数组中,直接返回-1,避免不必要的循环运算。

  3. 接下来,代码定义了两个变量:leftright,它们分别代表了当前查找范围的左边界和右边界。初始时,left被设为0,表示查找范围的起始位置,而right被设为nums.length - 1,表示查找范围的结束位置。

  4. 然后,代码进入一个循环体,条件是left <= right,即只要查找范围仍然有效,就会继续进行查找。

  5. 在循环中,首先计算中间索引mid,这是通过将leftright相加再除以2来得到的。这样可以获得当前查找范围的中间位置。

  6. 接下来,代码使用nums[mid]与目标整数target进行比较:

    • 如果nums[mid]等于target,则找到了目标整数,直接返回mid作为目标整数在数组中的索引。
    • 如果nums[mid]小于target,则说明目标整数可能在当前中间元素的右侧,因此将left更新为mid + 1,缩小查找范围。
    • 如果nums[mid]大于target,则说明目标整数可能在当前中间元素的左侧,因此将right更新为mid - 1,同样是为了缩小查找范围。
  7. 循环会不断根据比较结果缩小查找范围,直到查找范围变为无效(left > right),此时退出循环。

  8. 最后,如果循环结束时仍未找到目标整数,说明目标整数不在数组中,返回-1作为结果。

关于计算中间位置mid这行代码的解释:

int mid = left + ((right - left) >> 1);

eg: 1 + (99-1)/2 = 50

       97  + (99 - 97)/2 = 98

这里使用了位运算,具体来说是右移运算 >>,用于对 (right - left) 的结果进行右移一位。这个位运算的效果相当于将 (right - left) 的值除以 2,但是比直接使用除法更加高效。

解释这个表达式的步骤如下:

  1. (right - left):计算当前查找范围的长度,即右边界减去左边界,得到的值表示当前查找范围内元素的个数。

  2. ((right - left) >> 1):将上一步计算的查找范围长度右移一位,相当于将其除以 2。这是因为右移一位等价于除以 2 的操作,只是在二进制表示中更高效。

  3. left + ((right - left) >> 1):将右移后的结果加上左边界 left,得到的就是当前查找范围的中间索引 mid

这种方式计算中间索引是为了避免整数溢出问题,同时保持查找范围的均衡性。通过这种方法,可以在每次迭代中确定中间索引,将查找范围分成两半,以便在数组中进行更精确的查找操作。

为什么不使用: int mid = (right + left) >> 1;来进行计算呢?

虽然 (right + left) >> 1 在某些情况下可能会产生正确的结果,但在其他情况下可能会导致错误的结果或者整数溢出的问题。这是因为 (right + left) 的求和操作可能会导致整数溢出,尤其是在 rightleft 很大的情况下。

二进制位运算 >> 可以理解为将数值向右移动,并且在右边补零。但是如果 (right + left) 发生了溢出,移位后的结果可能不再是正确的中间索引,因为溢出后的数值可能与原来的意图不符。

相比之下,使用 (right - left) >> 1 的方式,即先计算差值再右移,可以减少整数溢出的风险。因为当 rightleft 都是正整数时,其差值不会超过 right 的值,从而避免了溢出的问题。

因此,在进行二分查找时,使用 (right - left) >> 1 来计算中间索引是更为安全和可靠的方法,可以有效避免溢出问题,同时保持算法的正确性和稳定性。

27 移除元素 

f4f0d74ec1ef4bb58a509796d76a3fef.png

2930ea3b97124915957d965c1b1c729b.png

这道题在力扣上暴力解法是可以通过的,我们先来看第一次写的暴力解法的问题(没过):

class Solution {
    public int removeElement(int[] nums, int val) {
        if(nums.length > 0){
            for(int i = 0;i <= nums.length;i++){
                if(val == nums[i]){
                    for(int j = i+1;j < nums.length;j++){
                        nums[j-1] = nums[j];
                    }
                }
            }
        }
        System.out.print(nums);
        return nums.length;
    }
}

问题:

    1.    循环条件错误:在外层循环中,i 的范围应该是 i < nums.length 而不是 i <= nums.length。
    2.   返回值问题:虽然代码中返回了 nums.length,但是数组中被修改的部分可能不再包含有效元素,这会导致返回的长度大于实际有效长度。

fe6f922ff976456ba7570c71061abab8.png

修改代码之后又发现问题:在数组中的数据往前替换了之后,i的值也需要往前走一位,不然就会出现轮空的情况。并且i和j的循环要小于size,而不是nums的长度,否则会出现size为负数的情况,时间也会超出限制。

7304c7ab0ecf4313b521d57866332bab.png

以上是暴力解法需要注意的地方。

下面我们看一下使用双指针法来进行计算。

 双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

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

双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。

class Solution {
    public int removeElement(int[] nums, int val) {
        // 快慢指针
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
            if (nums[fastIndex] != val) {
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
        }
        return slowIndex;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值