bisect_left本身不负责去重,因为它只定位插入点而不修改列表或判断重复;所谓“精确去重”需结合其返回值识别首次出现位置,并手动跳过后续重复项。

为什么 bisect_left 本身不负责去重
bisect_left 只是定位插入点,它不修改原列表,也不判断重复。所谓“用 bisect_left 精确去重”,其实是借助它的返回值,在已排序列表中识别重复元素的**首次出现位置**,再配合手动遍历或构建新列表来跳过后续重复项。
常见误解是以为调用一次 bisect_left 就能删掉所有重复——它连元素都不看,只看目标值该插在哪。真正去重得靠你控制遍历逻辑。
如何用 bisect_left + bisect_right 找出某值的完整重复区间
对已排序列表 arr 中任意值 x,bisect_left(arr, x) 返回第一个 x 的索引,bisect_right(arr, x) 返回第一个大于 x 的索引。两者之差就是 x 的重复次数。
- 若
bisect_right(arr, x) - bisect_left(arr, x) == 1,说明x只出现一次 - 若结果 > 1,则从
bisect_left(arr, x)开始连续多个位置都是x - 这个技巧在需要统计频次、或跳过整段重复时很稳,比逐个比较
arr[i] == arr[i-1]更安全(尤其含NaN或自定义对象时)
示例:
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
import bisect arr = [1, 2, 2, 2, 3, 4, 4, 5] x = 2 left = bisect.bisect_left(arr, x) # → 1 right = bisect.bisect_right(arr, x) # → 4 print(arr[left:right]) # [2, 2, 2]
原地去重但保持排序:用双指针 + bisect_left 跳过重复块
如果想复用原列表空间、避免新建大列表,可以用读写双指针,每次用 bisect_left 找到下一个**不同值**的起始位置,直接跳过去。
- 设
write指向下一个要写入的位置,read指向当前待处理位置 - 每次取
arr[read],用bisect_left(arr, arr[read], read)定位它第一次出现的位置(其实就等于read),再用bisect_right找到这块重复结束处 - 把该值写入
arr[write],然后write += 1,read更新为重复块之后的位置 - 注意
bisect_left第三个参数lo必须传read,否则可能找到前面已处理过的相同值
关键代码片段:
import bisect
def dedupe_inplace(arr):
if not arr: return 0
write = 0
read = 0
while read < len(arr):
val = arr[read]
# 从 read 开始找 val 第一次出现位置(就是 read 自己)
left = bisect.bisect_left(arr, val, read)
right = bisect.bisect_right(arr, val, read)
arr[write] = val
write += 1
read = right # 直接跳到下一个不同值
return write容易被忽略的边界:空列表、单元素、全重复、浮点误差
用 bisect 做去重时,这几个点最容易出错:
-
bisect_left([], x)返回 0,但后续索引访问会越界,必须先判空 - 浮点数列表中,
0.1 + 0.2 == 0.3为False,用bisect_left查0.3可能找不到真实存在的近似值;此时应先归一化或改用容忍误差的搜索(bisect_left不支持) - 自定义类对象排序时,
bisect_left依赖__lt__,但去重逻辑还需确保__eq__与排序逻辑一致,否则bisect_right可能停不准 - 全重复列表(如
[5]*1000)下,bisect_right一次性跳到末尾,性能是 O(1),比循环快得多——这点常被低估
真正难的不是调用函数,而是想清楚:你要保留的是每个值的第一次出现,还是最后一次,或者按某种权重选一个;bisect_left 只帮你锁住“第一次”,剩下全是你的事。

















