@@ -382,47 +382,19 @@ function binarySearchRight(nums, target) {
382
382
383
383
#### 思维框架
384
384
385
- 不管是寻找最左插入位置还是后面的寻找最右插入位置,我们的更新指针代码都是一样的。即:
386
-
387
- ```
388
- l = mid + 1
389
- # or
390
- r = mid
391
- ```
392
-
393
- > 当然也有别的方式(比如 mid 不是向下取整,而是向上取整), 但是这样可以最小化记忆成本。
394
-
395
- 有的人有疑问,这样设置结束条件会不会漏过正确的解,必然是不会的,如果更新区间代码是 r = mid - 1 还有可能错过,而 r = mid 这种代码根本就没有摒弃 r。
396
-
397
- 这样描述可能不够清晰。为了探究这个问题, 我们继续往下看。
398
-
399
- 以 nums = [ 1,3,4] , target = 2 为例。我们要找的其实就是 [ 1,x,3,4] 中 x 所在的位置,换句话说** 我们的目的就是不断收缩搜索区间到 x 位置** 。
400
-
401
- - 如果 nums[ mid] < x,由于 nums 是单调增的,因此 mid 一定在 x 左侧,小于等于 mid 部分一定不是最终解,我们将 mid + 1 作为新的搜索区间的左区间。
402
- - 如果 nums[ mid] >= x,由于 nums 是单调增的,因此 mid 一定在 x 的位置或者在 x 的右侧,由于 mid 可能就在 x 的位置,所以 r = mid - 1 就可能错过了这种解,使用 r = mid 这种更新方式可避免这种情况。
403
-
404
- 说得再直白一点。假设使用最左插入位置的方式查找的索引 为 i,那么应该满足不等式 nums[ i] >= target。
405
-
406
- - nums = [ 1,3,4] , target = 2 的情况 i = 1,nums[ i] = 3 > 2
407
- - nums = [ 1,2,2,2,3,4] , target = 2 的情况 i = 1,nums[ i] = 2 = 2
408
-
409
- 因此
410
-
411
- - 如果 nums[ mid] 已经小于 target 了,就必然是不可能的情况,直接通过 l = mid + 1 排除
412
- - 否则无法排除, 使用 r = mid 即可, mid 就是我们找到的一个备胎。
385
+ 如果你将** 寻找最左插入位置** 看成是** 寻找最左满足** 大于等于 x 的值,那就可以和前面的知识产生联系,使得代码更加统一。唯一的区别点在于** 前面是最左满足等于 x** ,这里是** 最左满足大于等于 x** 。
413
386
414
387
具体算法:
415
388
416
389
- 首先定义搜索区间为 [ left, right] ,注意是左右都闭合,之后会用到这个点。
417
390
418
391
> 你可以定义别的搜索区间形式,不过后面的代码也相应要调整,感兴趣的可以试试别的搜索区间。
419
392
420
- - 由于我们定义的搜索区间为 [ left, right] ,因此当 left <= right 的时候,搜索区间都不为空。 但由于上面提到了更新条件有一个 ` r = mid ` ,因此如果结束条件是 left <= right 则会死循环。因此结束条件是 left < right。
393
+ - 由于我们定义的搜索区间为 [ left, right] ,因此当 left <= right 的时候,搜索区间都不为空。 也就是说我们的终止搜索条件为 left <= right。
421
394
422
- - 循环体内,我们不断计算 mid ,并将 nums[ mid] 与 目标值比对。
423
- - 如果 nums[ mid] 大于等于目标值(mid 在 x 位置或者 x 的右侧), r 也可能是目标解(等于的情况),r - 1 可能会错过解,因此我们使用 r = mid。
424
- - 如果 nums[ mid] 小于目标值(mid 在 x 的左侧), mid 以及 mid 左侧都不可能是解,至少是 mid + 1 才行,因此我们使用 l = mid + 1。
425
- - 最后直接返回 l 或者 r 即可。(并且不需要像` 最左满足条件的值 ` 那样判断了)
395
+ - 当 A[ mid] >= x,说明找到一个备胎,我们令 r = mid - 1 将 mid 从搜索区间排除,继续看看有没有更好的备胎。
396
+ - 当 A[ mid] < x,说明 mid 根本就不是答案,直接更新 l = mid + 1,从而将 mid 从搜索区间排除。
397
+ - 最后搜索区间的 l 就是最好的备胎,备胎转正。
426
398
427
399
#### 代码模板
428
400
@@ -433,14 +405,11 @@ def bisect_left(nums, x):
433
405
# 内置 api
434
406
bisect.bisect_left(nums, x)
435
407
# 手写
436
- l, r = 0 , len (nums ) - 1
437
- while l < r:
408
+ l, r = 0 , len (A ) - 1
409
+ while l <= r:
438
410
mid = (l + r) // 2
439
- if nums[mid] < x:
440
- l = mid + 1
441
- else :
442
- r = mid
443
- # 由于 l 和 r 相等,因此返回谁都无所谓。
411
+ if A[mid] >= x: r = mid - 1
412
+ else : l = mid + 1
444
413
return l
445
414
```
446
415
@@ -450,22 +419,19 @@ def bisect_left(nums, x):
450
419
451
420
#### 思维框架
452
421
453
- 和 ` 寻找最左插入位置 ` 类似。不同的地方在于:如果有多个满足条件的值,我们返回最右侧的。 比如一个数组 nums: [ 1,2,2,2,3,4 ] ,target 是 2,我们应该插入的位置是 4 。
422
+ 如果你将 ** 寻找最右插入位置 ** 看成是 ** 寻找最右满足 ** 大于 x 的值,那就可以和前面的知识产生联系,使得代码更加统一。唯一的区别点在于 ** 前面是最左满足等于 x ** ,这里是 ** 最左满足大于 x ** 。
454
423
455
424
具体算法:
456
425
457
426
- 首先定义搜索区间为 [ left, right] ,注意是左右都闭合,之后会用到这个点。
458
427
459
428
> 你可以定义别的搜索区间形式,不过后面的代码也相应要调整,感兴趣的可以试试别的搜索区间。
460
429
461
- - 由于我们定义的搜索区间为 [ left, right] ,因此当 left <= right 的时候,搜索区间都不为空。 但由于上面提到了更新条件有一个` r = mid ` ,因此如果结束条件是 left <= right 则会死循环。因此结束条件是 left < right。
462
-
463
- > 有的人有疑问,这样设置结束条件会不会漏过正确的解,其实不会。举个例子容易明白一点。 比如对于区间 [ 4,4] ,其包含了一个元素 4,搜索区间不为空。如果我们的答案恰好是 4,会被错过么?不会,因为我们直接返回了 4。
430
+ - 由于我们定义的搜索区间为 [ left, right] ,因此当 left <= right 的时候,搜索区间都不为空。 也就是说我们的终止搜索条件为 left <= right。
464
431
465
- - 循环体内,我们不断计算 mid ,并将 nums[ mid] 与 目标值比对。
466
- - 如果 nums[ mid] 小于等于目标值, mid 以及 mid 左侧都不可能是解,因此我们使用 l = mid + 1。
467
- - 如果 nums[ mid] 大于目标值, r 也可能是目标解,r - 1 可能会错过解,因此我们使用 r = mid。
468
- - 最后直接返回 l 或者 r 即可。(并且不需要像` 最左满足条件的值 ` 那样判断了)
432
+ - 当 A[ mid] > x,说明找到一个备胎,我们令 r = mid - 1 将 mid 从搜索区间排除,继续看看有没有更好的备胎。
433
+ - 当 A[ mid] <= x,说明 mid 根本就不是答案,直接更新 l = mid + 1,从而将 mid 从搜索区间排除。
434
+ - 最后搜索区间的 l 就是最好的备胎,备胎转正。
469
435
470
436
#### 代码模板
471
437
@@ -477,14 +443,11 @@ def bisect_right(nums, x):
477
443
# 内置 api
478
444
bisect.bisect_right(nums, x)
479
445
# 手写
480
- l, r = 0 , len (nums ) - 1
481
- while l < r:
446
+ l, r = 0 , len (A ) - 1
447
+ while l <= r:
482
448
mid = (l + r) // 2
483
- if nums[mid] > x:
484
- r = mid
485
- else :
486
- l = mid + 1
487
- # 由于 l 和 r 相等,因此返回谁都无所谓。
449
+ if A[mid] <= x: l = mid + 1
450
+ else : r = mid - 1
488
451
return l
489
452
```
490
453
0 commit comments