数组 part01

103 阅读4分钟
学习任务
  1. 二分查找
  2. 移除元素
二分查找

题目是 leetCode 704 题 leetcode.cn/problems/bi…

这道题使用二分查找的方式解题,有一个前提是为什么会想到用二分查找来解题?或者说题目满足什么条件,可以想一想是不是可以用二分法来解决。

  • 有序数组
  • 数组中无重复的元素

二分法一般有两种写法:左闭右闭 [left right] 和左闭右开 [left right)

这里有两点需要注意:

  1. while循环中 left 到底是 < 还是 <= right
  2. if判断中 left 或者 right更新时,如何依据middle取值

所以在左闭右闭 [left right] 和左闭右开 [left right) 这两种二分写法中,对于以上两点思路完全不同,注意区分。

左闭右闭 [left right]

对于左闭右闭这种二分法,先思考left 到底是 < 还是 <= right,由于left == right是有意义的,所以使用<=

再来思考第二个问题,就是更新left 或者 right时,如何取值,这里使用如下图一个例子作为演示: 定义数组如下,target = 2

image.png 这里我们知道nums[middle] > target所以我们要更新搜索范围的左下标,关键需要注意 right 是应该取middle还是取middle - 1呢?

由于我们这里是左闭右闭 [left right]left可以等于 right,在nums[middle] > target我们已经比较过nums[middle]target的大小了,再赋值right时,就没有必要再比较一遍了,所以right = middle - 1

同理判断 nums[middle] < targetnums[middle] = target 的情况即可,具体代码实现如下。

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    // right是数组最后一个数的下标,num[right]在查找范围内,是左闭右闭区间
    let mid, left = 0, right = nums.length - 1;
    // 当left=right时,由于nums[right]在查找范围内,所以要包括此情况
    while (left <= right) {
        // 位运算 + 防止大数溢出(右移一位,相当于 / 2,向下取整)
        mid = left + ((right - left) >> 1);
        // 如果中间数大于目标值,要把中间数排除查找范围,所以右边界更新为mid-1;如果右边界更新为mid,那中间数还在下次查找范围内
        if (nums[mid] > target) {
            right = mid - 1;  // 去左面闭区间寻找
        } else if (nums[mid] < target) {
            left = mid + 1;   // 去右面闭区间寻找
        } else {
            return mid;
        }
    }
    return -1;
};
左闭右开 [left right)

左闭右开这种写法,由于右边是开区间,取不到right的值,所以在while循环中 left < right,如果 left = right就会出现如[1 1)即取到又取不到的情况,就不合法了。

关于第二个问题,还是如下这个例子: image.png

由于right本身是开区间,也就是取不到right,所以这里的right = middle,这也是和左闭右闭 [left right]的区别。

同理判断 nums[middle] < targetnums[middle] = target 的情况即可,具体代码实现如下。

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    // right是数组最后一个数的下标+1,nums[right]不在查找范围内,是左闭右开区间
    let mid, left = 0, right = nums.length;    
    // 当left=right时,由于nums[right]不在查找范围,所以不必包括此情况
    while (left < right) {
        // 位运算 + 防止大数溢出
        mid = left + ((right - left) >> 1);
        // 如果中间值大于目标值,中间值不应在下次查找的范围内,但中间值的前一个值应在;
        // 由于right本来就不在查找范围内,所以将右边界更新为中间值,如果更新右边界为mid-1则将中间值的前一个值也踢出了下次寻找范围
        if (nums[mid] > target) {
            right = mid;  // 去左区间寻找
        } else if (nums[mid] < target) {
            left = mid + 1;   // 去右区间寻找
        } else {
            return mid;
        }
    }
    return -1;
};
移除元素

学习 leetCode 27 题: leetcode.cn/problems/re…

这个题目如果使用两层for循环一层for循环遍历数组元素 ,第二个for循环更新数组,这种暴力解法的时间复杂度是O(n^2)。

可以使用双指针法来降低复杂度,通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

关键是要理解快慢指针的定义:

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置
//时间复杂度:O(n)
//空间复杂度:O(1)
var removeElement = (nums, val) => {
    let k = 0;
    for(let i = 0;i < nums.length;i++){
        if(nums[i] != val){
            nums[k++] = nums[i]
        }
    }
    return k;
};

新数组更新之后的长度就是k