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

704. 二分查找

首先介绍一下二分法。对于元素有序且不重复的数组,可以使用二分法查找某个特定元素的下标。

二分法简单来说就是循环的将数组对半分,每次都先取一个最中间的值,判断要找的元素比这个中间值大还是小,如果元素比中间值小,就将搜索区间变为上一次的搜索区间的前一半,反之就将搜索区间变为上一次的搜索区间的后一半。第一次搜索之前,将搜索区间设为整个数组。

搜索区间的变化可以使用left和right两个变量来实现,这两个变量也可以理解为指针。left和right的作用是划定要搜索的数组下标。left的初值自然是数组第一个的元素的下标,也就是0。right的初值和经过搜索后的值与区间的设定有关。最常被使用的区间是左闭右开和左闭右闭区间。

如果是左闭右开区间,则区间内的最后一个下标值对应的元素不会被搜索,需要让right的值比最后一个元素的下标大,才能保证数组内的所有元素都被搜索,因此应该将其设为nums.length。

然而,如果是左闭右闭区间, 区间内所有的下标值对应的元素都需要被搜索,此时right的初值应为数组最后一个元素的下标,nums.length - 1。  

同时,区间设定的不同也会造成循环条件和右边界重新设定的轻微不同。左闭右开区间的循环条件为left < right,因为使用左闭右开区间时,left不能等于right。当target小于中间元素,需要重新确定右边界时,右边界也应该设为middle,因为右边界不会被搜索。但是对于左闭右闭区间就要使用left <= right,否则可能会遗漏元素。重新确定右边界时也应该设为middle-1。

LeetCode704即为一个最简单的二分查找的实现。以下是本题Java版的参考答案: 

参考答案 

注意,对于middle的计算,推荐使用右移(>>)而不是直接用left + right / 2,因为当left + right很大时有可能超过基本类型所能容纳的最大值,使用位运算可以避免这个问题,并且位运算的速度比直接使用除更快。

// 左闭右开
class Solution {
    public int search(int[] nums, int target){
        int left = 0;
        int right = nums.length;
        while (left < right) {
            int middle = left + ((right - left) >> 1);
            if (target < nums[middle]) {
                right = middle;
            } else if (target > nums[middle]) {
                left = middle + 1;
            } else {
                return middle;
            }
        }
        return -1;
    }
}
// 左闭右闭
class Solution {
    public int search(int[] nums, int target){
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int middle = left + ((right - left) >> 1);
            if (target < nums[middle]) {
                right = middle - 1;
            } else if (target > nums[middle]) {
                left = middle + 1;
            } else {
                return middle;
            }
        }
        return -1;
    }
}

27. 移除元素 

本题可以直接使用暴力解法做出,不过一般使用的解法还是双指针解法,时间复杂度更低。

暴力解法就是直接遍历数组,碰到与要求的val相同的元素就用后一个元素替换。不过在替换时需要引入j再写一个循环,因为不用j直接写num[i]=num[i+1]的话,如果最后一个元素需要被替换,程序将无法找到num[i+1]。在遍历之前先将数组长度定义为原始长度,每替换一个元素,数组长度就-1,同时,i也需要-1,因为此位置的元素已被替换,要通过i--来重新对此位置上元素的值进行检查。

双指针法(快慢指针法)是指通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。在本题中快指针用来遍历原数组,慢指针只指向不需要被移除的元素,用来记录新数组的长度。碰到要移除的元素时,慢指针不动。直到遍历到不需移除的元素时,才将慢指针的位置填充上快指针指向的元素,之后移动一位慢指针。

这两个解法其实有相似之处。快指针的作用与暴力解法中用来遍历的i一样,因此我的代码中直接将快指针记作i。慢指针用来记录新数组的长度,也直接记为length。只不过在暴力解法中,length的初值为数组长度,随着元素被后一个元素替换而减少;而在双指针法中,length直接指向新数组的下标,因此初值为0,只有快指针找到不为val的元素时,才将那个元素的值赋给慢指针指向的下标,然后增加length的值(这一步也意味着一个元素更新完成,慢指针向后移动)。

以下是本题Java版的参考答案:  

参考答案

// 暴力解法
class Solution {
    public int removeElement(int[] nums, int val) {
        int length = nums.length;
        for (int i = 0; i < length; i++) {
            if (nums[i] == val) {
                // 需要引入j而不是直接使用num[i] = num[i+1],
                // 否则替换最后一个元素时会因为num[i+1]不存在而越界
                for (int j = i + 1; j < length; j++) {
                    nums[j - 1] = nums[j];
                }
                length--;
                i--;
            }
        }
        return length;
    }
}
// 双指针
class Solution {
    public int removeElement(int[] nums, int val) {
        // 将新数组长度初始为0
        int length = 0;
        for (int i = 0; i < nums.length; i++) {
            // 只有当找到值不为val的元素时,才向新数组的慢指针位置填充元素
            if (nums[i] != val) {
                // 原数组的前length-1个元素都会被更新,确保它们是值不为val的元素
                nums[length] = nums[i];
                // 同时更新此次操作后的新数组长度
                length++;
            }
        }
        return length;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值