文章目录
把这段代码想象成一个"整理高矮队伍的教官"。教官手头有一排乱糟糟的人,任务是把矮的排左边,高的排右边。
第一步:找一个"裁判",并防范有人"作弊"
python
rand_idx = random.randint(l, r)
mid = (l + r) // 2
q[mid], q[rand_idx] = q[rand_idx], q[mid]
x = q[mid]
- 普通操作 :教官每次都固定挑正中间那个人当身高基准 x x x("比他矮的站左边,比他高的站右边")。
- 为什么被卡超时? LeetCode 就像一个存心搞破坏的刺头,提前摸清了你每次都选正中间,于是故意排出一个特制队形,让你每次选的中点要么是队伍里最高的,要么是最矮的。结果你每次切队伍只能切下 1 个人,工作量直接爆炸成 O ( n 2 ) O(n^2) O(n2)。
- 破局妙招(这 3 行代码) :教官在选基准前掷一次骰子 ,从队伍里随机抓一个人换到中间。这下出题人再怎么设计队形也没用,因为你选基准的位置是随机的,每次都能大概率把队伍对半切开,时间稳保 O ( n log n ) O(n \log n) O(nlogn)。
第二步:两个侦察兵进场(指针初始化)
python
i, j = l - 1, r + 1
- 左边派一个兵 i i i,右边派一个兵 j j j。
- 为什么站在边界外面( l − 1 l-1 l−1 和 r + 1 r+1 r+1)?因为教官下达的死命令是:只要开始巡逻,第一件事必须先往前迈一步! 站在场外,一迈步正好踏入合法队伍的第一个和最后一个位置。
第三步:相向而行,互换违规人员
python
while i < j:
while True:
i += 1
if q[i] >= x:
break
while True:
j -= 1
if q[j] <= x:
break
if i < j:
q[i], q[j] = q[j], q[i]
- 左兵 i i i 出发 :向右走,只要看到比裁判矮( < x < x <x)的就放行;一旦抓到一个"身高 ≥ x \ge x ≥x 却站在左边"的违规大个子,立刻停下(
break)。 - 右兵 j j j 出发 :向左走,只要看到比裁判高( > x > x >x)的就放行;一旦抓到一个"身高 ≤ x \le x ≤x 却站在右边"的违规矮个子,也立刻停下(
break)。 - 现场交换 :如果两人还没撞上( i < j i < j i<j),说明抓到了两个站错方向的家伙,直接把他们对调位置 (
q[i], q[j] = q[j], q[i])。 - 循环往复 ,直到两个侦察兵擦肩而过( i ≥ j i \ge j i≥j),此时队伍就达到了一个状态:j j j 及 j j j 左边的人,身高全部 ≤ x \le x ≤x; j + 1 j+1 j+1 及右边的人,身高全部 ≥ x \ge x ≥x。
这里为什么用
while True+>= / <=?如果队伍里有大量"身高一模一样"的人,这个机制能保证 i i i 和 j j j 每次抓到一样高的也会各走一步互换,最后刚好在正中间相遇交错,绝不会卡在同一个位置原地踏步(死循环)。
第四步:任务拆分,递归解决
python
qs(q, l, j)
qs(q, j + 1, r)
教官现在发现:队伍已经被 j j j 完美分成了左半段(都不高于 x x x)和 右半段(都不低于 x x x) 。
接下来教官不再管细节,把左半段交给副官 A 去用同样的方法排,右半段交给副官 B 去排。子队伍越拆越小,拆到队伍里只有 1 个人( l ≥ r l \ge r l≥r)时自然就有序了,直接返回。
极简口诀复盘
- 随机换中间:掷骰子破定点针对。
- 两头向内迈:先走一步再核对。
- 违规就交换:矮去左边高去右。
- 切分靠 j j j 标:左右分治排到底。