刷完这19道leetcode二分查找算法,不信进不了大厂

对于二分题,其实就是设定一个中间值 mid, 然后通过这个值进行一个判断 check(mid), 通过这个函数的返回值,判断将不可能的一半剪切掉;

在刷题的时候需要注意主要是两部分,check 函数的定义以及边界的选择(等号的选择,以及最后是 return left 还是 right)

这次主要是 LC 的二分专题,里面的简单题基本都是比较显性的提示了 check 函数的构建,比方说直接找出某个值,而难题一般都是 check 函数比较难想的,这个时候就需要经验了;

广义上只要是排好序(局部排序),只要是找某个值,大部分都可以考虑用二分,这样复杂度可以降低很多;

对于边界,我的循环结束条件是 left <= right , 因为如果要多记很多模板,怕会出问题,所以退出条件基本都按这个,然后无论是那种模块,都基于这个结束条件来判断,这样可以把问题收缩都循环里的判定的 check 函数,多做了就会发现端倪;

然后关于退出之后 left 还是 right ,这个是具体问题具体分析;由于我的结束判定条件是 left<=right ,所以如果没用中间返回,那么必然存在 left === right 的时候,这个时候根据判定条件,就知道 right 在 left 的前面,而到底是左逼近,还是右逼近,都比较好判断了,因为这个时候已经退出去了,left 和 right 所代表的 check 的状态也是显而易见的,那么看题目要求什么,给什么即可;

对于二分,我觉得这个专题就基本足够了,简单居多,难题也有两个;如果是第一次学习二分,那么按照专栏的三个模板去记忆也 ok, 别人的经验终归是适合别人自己,做题最重要是把握住自己的节奏,记忆自己最熟悉的那个点,强行模仿别人反而落了下乘;

当然那个男人那么强,我的做题就是模仿的他,慢慢大佬的解法就是我自己的节奏了,毕竟模仿多了,其实就是自己的了,除了算法,其他的工程化学习也是一样的;

那么,周末快乐,下周开 dp 吧,毕竟这个听有意思的。

模板 1

  • 目标值是一个固定的 target,在二分过程中需要不断的判断,如果成功就返回对应的值,否则直接返回失败的值
  • 返回值如果是向下取,返回 right,如果向上取,则返回 left,还有可能返回一个特定给的失败值;
var search = function (fn, target) {
   
  let left = 最小值,
    right = 最大值;
  while (left <= right) {
   
    // 取 mid 值
    const mid = ((right - left) >> 1) + left;
    //这里的 fn 可能是函数,也可能只是数组取值,反正就是可以取得一个值去跟 target 比较
    const temp = fn(mid);
    if (temp === target) return mid;
    if (temp < target) {
   
      left = mid + 1;
    } else {
   
      right = mid - 1;
    }
  }
  return 没有精确匹配后的值;
};

704. 二分查找

var search = function (nums, target) {
   
  const len = nums.length;
  if (!len) return -1;
  let left = 0,
    right = len - 1;
  while (left <= right) {
   
    const mid = ((right - left) >> 1) + left;
    if (nums[mid] === target) return mid;
    if (nums[mid] < target) {
   
      left = mid + 1;
    } else {
   
      right = mid - 1;
    }
  }
  return -1;
};

69. x 的平方根

// 69. x 的平方根
var mySqrt = function (x) {
   
  let left = 0,
    right = x;
  while (left <= right) {
   
    const mid = ((right - left) >> 1) + left;
    const sqrt = mid * mid;
    if (sqrt === x) return mid;
    if (sqrt < x) {
   
      left = mid + 1;
    } else {
   
      right = mid - 1;
    }
  }
  // 向下取整
  return right;
};

374. 猜数字大小

分析

  1. 这里内置一个函数 guess(n), 返回值是 -1 0 1, -1 是 targt 值更小
var guessNumber = function (n) {
   
  let left = 1,
    right = n;
  while (left <= right) {
   
    const mid = ((right - left) >> 1) + left;

    if (guess(mid) === 0) return mid;
    if (guess(mid) > 0) {
   
      // 这个时候 mid < pick
      left = mid + 1;
    } else {
   
      right = mid - 1;
    }
  }
};

// 自己模拟一下这个 guess 函数吧 -- 假定第二个参数就是目标猜的数字,我们可以用它来初始化,默认是5
function guess(num, pick = 5) {
   
  if (num === pick) return 0;
  if (pick < num) return -1;
  if (pick > num) return 1;
}

参考视频:传送门

441. 排列硬币

分析

  1. 这里求的是一个左侧极值的二分法,是向右逼近的二分
  2. 累计值算法是小学数学题 sum = (first+end)*count/2
  3. 每次取中间层数,求出到这个层数需要的币数 sum,然后和目标值 n 比较
  4. 如果刚好符合,直接返回(这里可以收缩到左侧判定条件中);如果 count 比较少,则 left 要提到 mid+1,否则 right 要提到 mid-1
  5. 由于最后要返回的是最逼近 n 的层数,所以判断一下当 left === right 情况,如果小于 n,则 left = mid+1,这个时候 right 符合要求,所以跳出循环后,返回的是 right
  6. 时间复杂度O(logN)
var arrangeCoins = function (n) {
   
  let left = 0,
    right = n;
  while (left <= right) {
   
    const mid = left + ((right - left) >> 1);
    // mid 层的时候满的硬币数
    const sum = ((1 + mid) * mid) / 2;
    if (sum === n) return mid;
    if (sum < n) {
   
      left = mid + 1;
    } else {
   
      right = mid - 1;
    }
  }
  return right;
};

33. 搜索旋转排序数组

分析

  1. 已知:原始数组 nums 是生序排序的,且数组中的值不一样的
  2. 入参的 nums 是在某个下标 k 的作用下发生了重置,使得 nums 现在是先升序数组 [k,len-1]然后断裂后,再一个升序数组[0,k-1]
  3. 这是一个局部排好序的数组,所以可以用二分处理,返回的是 target 值的下标或者 -1
  4. 所以每次都用排好序的一半来作为判断依据,如果在排好序这边,则删除另外,反之亦然
  5. 时间复杂度 O(logn)
var search = function (nums
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值