[算法训练营]数组中的双指针(Day1)

200 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

这些东西来来回回看,忘了看,看了忘🤦‍。

二分查找

原理:

二分查找的前提条件就是,单调增,无重复数

  • 如果target<middle(数列中间的那个值),那么将意味着middle后面所有值都大于target,所以只需要对middle之前进行比较
  • 同理,对于target>middle,那么只要对middle之后进行比较
  • 如果target = middle,那么就说找到了这个值

细节处理

关于是[,]还是[,)区间对应的不同解答

[left,right)

我的理解是right值永远不会是target对应下标的,每次搜索区间是[left,right-1]

  1. 初始化,right = nums.length

    因为nums[nums.length]也是永远无法取到的

  2. while(left<right)

    因为,跳出while循环的时候,条件是left === right,[left,left)这个区间是没有遗漏的。假如这个区间是[left,right],那么结束条件变为[left,left],这时候其实是遗漏了left这个值的。

  3. 并且当target<nums[mid]时,我们的right = mid

    因为我们已经知道nums[mid]一定不会等于target。也就是下一次我要查找的区间其实是[left,right-1]或者[left,right)

[left,right]

我的理解是:right是可能作为target对应下标的,也就是每次搜索区间是[left,right]

  1. right其实是可以取到的,所以他其实是数组最后一个元素的下标

    right = nums.length-1

  2. while(left<=right),跳出循环的条件是left === right+1,也就是[left,left+1],没有遗漏元素。同理,假如区间为[left,right),那么结束条件变为了[left,left+1),那么left就遗漏了。

  3. 那么当target<nums[mid]时,我们的right = mid-1

    正如上面说的,nums[mid]一定不会等于target。查找的区间其实是[left,mid-1],所以我们将right=mid-1

二分查找

题目:[二分查找](704. 二分查找 - 力扣(LeetCode) (leetcode-cn.com))

题解:

  • 为了方便显示,我们将mid用来显示中间的值

  • 注意,我们取中间的值的时候都是使用Math.floor()

  • 这是数学问题,取中间那个值mid = left + Math.floor((right - left) /2)

    但是我们推荐使用mid = left + Math.floor((right - left) /2),因为如果你使用Math.floor((left + right) / 2),可能会出现,leftright太大,然后相加出现溢出的情况

代码

function search(nums: number[], target: number): number {
  let right = nums.length - 1;
  let left = 0;
  let mid = 0;
  while (left <= right) {
    mid = left + Math.floor((right - left) /2);
    if (nums[mid] < target) {
      left = mid + 1;
    } else if (nums[mid] > target) {
      right = mid - 1;
    } else {
      return mid;
    }
  }
  return -1;
}

搜索插入位置

链接:35. 搜索插入位置 - 力扣(LeetCode)

题解:

  • 主要思路就是一个二分查找+插入

  • 如果可以找到这个值,那么皆大欢喜,直接返回下标即可

  • 如果没有扎到这个值,那么就意味着,已经跳出来while循环

    left === right+1了,才会跳出循环。

  • 这时候,将target放在left上最合适。因为刚刚已经搜索过了,left后面的值后面的值都是比target大的

代码:

function searchInsert(nums: number[], target: number): number {
    // 二分查找+插入值
    let left = 0;
    let right = nums.length - 1;
    while (left <= right) {
        let mid = left + Math.floor((right - left) / 2);
        if (target === nums[mid]) {
            return mid
        } else if (target < nums[mid]) {
            right = mid-1
        } else if (target > nums[mid]) {
            left = mid+1
        }
    }
    return left
};

在排列数组中查找元素的第一个和最后一个

题目:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

思路:

  • 其实是套了二分查找的外衣而已,因为二分查找,需要保证,单调增且无重复
  • 我们可以首先先找到target元素,然后在它的前后进行找
  • 最后返回范围即可[sleft+1,sright-1]
  • 为什么需要+1,-1呢,因为当最后一个满足nums[sleft] === target的时候,sleft--了,但是其实nums[sleft--] !== target,所以需要+1,同理,sright也是

代码:

function searchRange(nums: number[], target: number): number[] {
    let left = 0;
    let right = nums.length-1;
    while (left <= right) {
        let mid = left + Math.floor((right - left) / 2);
        if (nums[mid] < target) {
            left = mid+1
        } else if (nums[mid] > target) {
            right = mid-1
        } else if (nums[mid] === target) {
            // 这时候去查看前后
            let sleft = mid;
            let sright = mid;
            while (nums[sleft] === target) {
                sleft--
            }
            while (nums[sright] === target) {
                sright++
            }
            return [sleft+1,sright-1]
        }
    }
    return [-1,-1]
};

当然,其实还有一种思路,并且其实方法更好,但是我觉得,不那么好理解。

移除元素

要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

移除元素

题目:27. 移除元素 - 力扣(LeetCode)

思路:

  • 使用的是一种双指针的思想
  • slowfast同时前进,只有当nums[fast] !== val,才会对nums[slow]进行设置值。
  • 我的理解是:
    • 假如题目运行使用两个数组去完成。
    • 那么就可以在一次遍历的时候,current !== val,将他放到新的数组里面。
    • 其实我们nums[slow] = nums[fast]也是差不多的意思。我们现在想要维护的数组,其实只有nums[0...slow]
  • 因为数组在内存上连续的,所以不存在,所以其实不存在将数组中间的那个值删掉,其实本质上,他只是将你将要删除的值覆盖掉了
  • 并且题目表示,你不需要考虑数组中超出新长度后面的元素。意思就是,后面多出来的,fast-slow个值,可以不考虑。

代码:

function removeElement(nums: number[], val: number): number {
    // 使用快慢指针
    let fast = 0;
    let slow = 0;
    // 两者是一起走的
    while (fast < nums.length) {
        if (nums[fast] !== val) {
            nums[slow] = nums[fast]
            slow++
        }
        fast++;
    }
    return slow
};

参考资料:代码随想录 (programmercarl.com)